home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Linux Cubed Series 7: Sunsite
/
Linux Cubed Series 7 - Sunsite Vol 1.iso
/
system
/
shells
/
zsh-3.0-p
/
zsh-3
/
zsh-3.0-pre3
/
Src
/
zle_tricky.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-07-14
|
103KB
|
3,978 lines
/*
* $Id: zle_tricky.c,v 2.64 1996/07/13 20:26:35 hzoli Exp $
*
* zle_tricky.c - expansion and completion
*
* This file is part of zsh, the Z shell.
*
* Copyright (c) 1992-1996 Paul Falstad
* All rights reserved.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* In no event shall Paul Falstad or the Zsh Development Group be liable
* to any party for direct, indirect, special, incidental, or consequential
* damages arising out of the use of this software and its documentation,
* even if Paul Falstad and the Zsh Development Group have been advised of
* the possibility of such damage.
*
* Paul Falstad and the Zsh Development Group specifically disclaim any
* warranties, including, but not limited to, the implied warranties of
* merchantability and fitness for a particular purpose. The software
* provided hereunder is on an "as is" basis, and Paul Falstad and the
* Zsh Development Group have no obligation to provide maintenance,
* support, updates, enhancements, or modifications.
*
*/
#define ZLE
#include "zsh.h"
/* The main part of ZLE maintains the line being edited as binary data, *
* but here, where we interface with the lexer and other bits of zsh, *
* we need the line metafied. The technique used is quite simple: on *
* entry to the expansion/completion system, we metafy the line in *
* place, adjusting ll and cs to match. All completion and expansion *
* is done on the metafied line. Immediately before returning, the *
* line is unmetafied again, changing ll and cs back. (ll and cs might *
* have changed during completion, so they can't be merely saved and *
* restored.) The various indexes into the line that are used in this *
* file only are not translated: they remain indexes into the metafied *
* line. */
#ifdef HAVE_NIS_PLUS
# include <rpcsvc/nis.h>
#else
# ifdef HAVE_NIS
# include <rpc/types.h>
# include <rpc/rpc.h>
# include <rpcsvc/ypclnt.h>
# include <rpcsvc/yp_prot.h>
/* This is used when getting usernames from the NIS. */
typedef struct {
int len;
char *s;
}
dopestring;
# endif
#endif
#define inststr(X) inststrlen((X),1,-1)
/* Prefix and suffix for globbing, used as an optimisation. */
extern char *glob_pre, *glob_suf;
/* wb and we hold the beginning/end position of the word we are completing. */
static int wb, we;
/* offs is the cursor position within the tokenized *
* current word after removing nulargs. */
static int offs;
/* These control the type of completion that will be done. They are *
* affected by the choice of ZLE command and by relevant shell options. */
static int usemenu, useglob;
/* A pointer to the current position in the menu-completion array (the one *
* that was put in the command line last). */
static char **menucur;
/* The point (in the command line) where the menu-completion strings are *
* inserted, the length of the string that was inserted last, the end *
* position of this string in the command line and two flags: menuwe is *
* non-zero if the cursor was at the end of the word (which means that we *
* can add suffixes, e.g. a slash for directories), and menuce is used to *
* save/restore the value of complexpect during consecutive menu *
* completions (complexpect is used when completing after `$' or `${' and *
* says whether some characters should be treated specially, when the *
* option autoparamkeys is set). menuinsc is nonzero if a trailing slash *
* marking a directory is added. */
static int menupos, menulen, menuend, menuwe, menuce, menuinsc;
/* The list of matches. fmatches contains the matches we first ignore *
* because of fignore. */
static LinkList matches, fmatches;
/* The list of matches turned into an array. This is used to sort this *
* list and when menu-completion is used (directly or via automenu). */
static char **amatches;
/* The number of matches. */
static int nmatches;
/* !=0 if we have a valid completion list. */
static int validlist;
/* This flag is non-zero if we are completing a pattern (with globcomplete) */
static int ispattern;
/* Two patterns used when doing glob-completion. The first one is built *
* from the whole word we are completing and the second one from that *
* part of the word that was identified as a possible filename. */
static Comp patcomp, filecomp;
/* We store the following prefixes/suffixes: *
* lpre/lsuf -- what's on the line *
* rpre/rsuf -- same as lpre/lsuf, but expanded *
* *
* ... and if we are completing files, too: *
* ppre/psuf -- the path prefix/suffix *
* fpre/fsuf -- prefix/suffix of the pathname component the cursor is in *
* prpre -- ppre in expanded form usable for opendir *
* *
* The integer variables hold the lengths of lpre, lsuf, rpre, rsuf, *
* fpre, and fsuf. noreal is non-zero if we have rpre/rsuf. */
static char *lpre, *lsuf;
static char *rpre, *rsuf;
static char *ppre, *psuf, *prpre;
static char *fpre, *fsuf;
static int lpl, lsl, rpl, rsl, fpl, fsl;
static int noreal;
/* This is used when completing after `$' and holds the whole prefix, *
* used in do_single() to check whether the word expands to a directory *
* name (in that case and if autoparamslash is set, we add a `/'). *
* qparampre is the same but quoted. The length of it is in qparprelen */
static char *parampre = NULL, *qparampre = NULL;
static int qparprelen;
/* This is either zero or equal to the special character the word we are *
* trying to complete starts with (e.g. Tilde or Equals). */
static char ic;
/* These hold the minimum common prefix/suffix lengths (normal and for *
* fignore ignored). */
static int ab, ae, fab, fae;
/* This variable says what we are currently adding to the list of matches. */
static int addwhat;
/* firstm hold the first match we found, shortest contains the shortest *
* one (normal and for fignore ignored). */
static char *firstm, *shortest, *ffirstm, *fshortest;
/* This holds the word we are completing in quoted from. */
static char *qword;
/* This is the length of the shortest match we found (normal and for *
* fignore ignored). */
static int shortl, fshortl;
/* This is non-zero if we are doing a menu-completion and this is not the *
* first call (e.g. when automenu is set and menu-completion was entered *
* due to this). */
static int amenu;
/* This is used by expandorcompleteprefix to save the position of the *
* inserted space (so that we can remove it later). */
static int remove_at = -1;
/* Find out if we have to insert a tab (instead of trying to complete). */
/**/
int
usetab(void)
{
unsigned char *s = line + cs - 1;
for (; s >= line && *s != '\n'; s--)
if (*s != '\t' && *s != ' ')
return 0;
return 1;
}
#define COMP_COMPLETE 0
#define COMP_LIST_COMPLETE 1
#define COMP_SPELL 2
#define COMP_EXPAND 3
#define COMP_EXPAND_COMPLETE 4
#define COMP_LIST_EXPAND 5
#define COMP_ISEXPAND(X) ((X) >= COMP_EXPAND)
/**/
void
completeword(void)
{
usemenu = isset(MENUCOMPLETE);
useglob = isset(GLOBCOMPLETE);
if (c == '\t' && usetab())
selfinsert();
else
docomplete(COMP_COMPLETE);
}
/**/
void
menucomplete(void)
{
usemenu = 1;
useglob = isset(GLOBCOMPLETE);
if (c == '\t' && usetab())
selfinsert();
else
docomplete(COMP_COMPLETE);
}
/**/
void
listchoices(void)
{
usemenu = isset(MENUCOMPLETE);
useglob = isset(GLOBCOMPLETE);
docomplete(COMP_LIST_COMPLETE);
}
/**/
void
spellword(void)
{
usemenu = useglob = 0;
docomplete(COMP_SPELL);
}
/**/
void
deletecharorlist(void)
{
char **mc = menucur;
usemenu = isset(MENUCOMPLETE);
useglob = isset(GLOBCOMPLETE);
if (cs != ll)
deletechar();
else
docomplete(COMP_LIST_COMPLETE);
menucur = mc;
}
/**/
void
expandword(void)
{
usemenu = useglob = 0;
if (c == '\t' && usetab())
selfinsert();
else
docomplete(COMP_EXPAND);
}
/**/
void
expandorcomplete(void)
{
usemenu = isset(MENUCOMPLETE);
useglob = isset(GLOBCOMPLETE);
if (c == '\t' && usetab())
selfinsert();
else
docomplete(COMP_EXPAND_COMPLETE);
}
/**/
void
menuexpandorcomplete(void)
{
usemenu = 1;
useglob = isset(GLOBCOMPLETE);
if (c == '\t' && usetab())
selfinsert();
else
docomplete(COMP_EXPAND_COMPLETE);
}
/**/
void
listexpand(void)
{
usemenu = isset(MENUCOMPLETE);
useglob = isset(GLOBCOMPLETE);
docomplete(COMP_LIST_EXPAND);
}
/**/
void
reversemenucomplete(void)
{
if (!menucmp) {
menucomplete();
return;
}
HEAPALLOC {
if (menucur == amatches)
menucur = amatches + nmatches - 1;
else
menucur--;
complexpect = menuce;
metafy_line();
do_single(*menucur);
unmetafy_line();
} LASTALLOC;
}
/* Accepts the current completion and starts a new arg, *
* with the next completions. This gives you a way to *
* accept several selections from the list of matches. */
/**/
void
acceptandmenucomplete(void)
{
if (!menucmp) {
feep();
return;
}
cs = menuend;
inststrlen(" ", 1, 1);
if (qparampre)
inststrlen(qparampre, 1, qparprelen);
if (lpre)
inststrlen(lpre, 1, -1);
if (lsuf)
inststrlen(lsuf, 0, -1);
menupos = cs;
menuend = cs + (lsuf ? strlen(lsuf) : 0);
menulen = 0;
menuwe = 1;
menucomplete();
}
/* These are flags saying if we are completing in the command *
* position or in a redirection. */
static int lincmd, linredir;
/* Non-zero if the last completion done was ambiguous (used to find *
* out if AUTOMENU should start). More precisely, it's nonzero after *
* successfully doing any completion, unless the completion was *
* unambiguous and did not cause the display of a completion list. *
* From the other point of view, it's nonzero iff AUTOMENU (if set) *
* should kick in on another completion (provided the text isn't *
* changed in the meantime... AAAAAAAAAAUUUUGGGHH!). */
static int lastambig;
/* This describes some important things collected during the last *
* completion. Its value is zero or the inclusive OR of some of *
* the HAS_* things below. */
static int haswhat;
/* We have a suffix to add (given with compctl -S). */
#define HAS_SUFFIX 1
/* We have filenames in the completion list. */
#define HAS_FILES 2
/* We have other things than files in the completion list. If this is *
* not set but HAS_FILES is, we probably put the file type characters *
* in the completion list (if listtypes is set) and we attempt to add *
* a slash to completed directories. */
#define HAS_MISC 4
/* This is set if we have filenames in the completion list that were *
* generated by a globcompletion pattern. */
#define HAS_PATHPAT 8
/* This holds the naem of the current command (used to find the right *
* compctl). */
static char *cmdstr;
/* Check if the given string is the name of a parameter and if this *
* parameter is one worth expanding. */
/**/
int
checkparams(char *p)
{
int t0, n, l = strlen(p), e = 0;
struct hashnode *hn;
for (t0 = paramtab->hsize - 1, n = 0; n < 2 && t0 >= 0; t0--)
for (hn = paramtab->nodes[t0]; n < 2 && hn; hn = hn->next)
if (pfxlen(p, hn->nam) == l) {
n++;
if (strlen(hn->nam) == l)
e = 1;
}
return (n == 1) ? (getsparam(p) != NULL) :
(!menucmp && e && isset(RECEXACT));
}
/* Check if the given string has wildcards. The difficulty is that we *
* have to treat things like job specifications (%...) and parameter *
* expressions correctly. */
/**/
int
cmphaswilds(char *str)
{
if ((*str == Inbrack || *str == Outbrack) && !str[1])
return 0;
/* If a leading % is immediately followed by ?, then don't *
* treat that ? as a wildcard. This is so you don't have *
* to escape job references such as %?foo. */
if (str[0] == '%' && str[1] ==Quest)
str += 2;
for (; *str;) {
if (*str == String || *str == Qstring) {
/* A parameter expression. */
if (*++str == Inbrace)
skipparens(Inbrace, Outbrace, &str);
else if (*str == String || *str == Qstring)
str++;
else {
/* Skip all the things a parameter expression might start *
* with (before we come to the parameter name). */
for (; *str; str++)
if (*str != '^' && *str != Hat &&
*str != '=' && *str != Equals &&
*str != '~' && *str != Tilde)
break;
if (*str == '#' || *str == Pound)
str++;
/* Star and Quest are parameter names here, not wildcards */
if (*str == Star || *str == Quest)
str++;
}
} else {
/* Not a parameter expression so we check for wildcards */
if (*str == Pound || *str == Hat || *str == Star ||
*str == Bar || *str == Quest ||
!skipparens(Inbrack, Outbrack, &str) ||
!skipparens(Inang, Outang, &str) ||
(!isset(IGNOREBRACES) &&
!skipparens(Inbrace, Outbrace, &str)) ||
(*str == Inpar && str[1] == ':' &&
!skipparens(Inpar, Outpar, &str)))
return 1;
if (*str)
str++;
}
}
return 0;
}
/* The main entry point for completion. */
/**/
void
docomplete(int lst)
{
char *s, *ol;
int olst = lst, chl = 0, ne = noerrs, ocs;
/* If we are doing a menu-completion... */
if (menucmp && lst != COMP_LIST_EXPAND) {
do_menucmp(lst);
return;
}
/* Check if we have to start a menu-completion (via automenu). */
if ((amenu = (isset(AUTOMENU) &&
(lastcmd & ZLE_MENUCMP) &&
lastambig)))
usemenu = 1;
/* Expand history references before starting completion. If anything *
* changed, do no more. */
if (doexpandhist())
return;
metafy_line();
ocs = cs;
if (!isfirstln && chline != NULL) {
/* If we are completing in a multi-line buffer (which was not *
* taken from the history), we have to prepend the stuff saved *
* in chline to the contents of line. */
ol = dupstring((char *)line);
/* Make sure that chline is zero-terminated. */
*hptr = '\0';
cs = 0;
inststr(chline);
chl = cs;
cs += ocs;
} else
ol = NULL;
inwhat = IN_NOTHING;
qword = NULL;
/* Get the word to complete. */
noerrs = 1;
s = get_comp_string();
DPUTS(wb < 0 || cs < wb || cs > we,
"BUG: 0 <= wb <= cs <= we is not true!");
noerrs = ne;
/* For vi mode, reset the start-of-insertion pointer to the beginning *
* of the word being completed, if it is currently later. Vi itself *
* would never change the pointer in the middle of an insertion, but *
* then vi doesn't have completion. More to the point, this is only *
* an emulation. */
if (viinsbegin > ztrsub((char *) line + wb, (char *) line))
viinsbegin = ztrsub((char *) line + wb, (char *) line);
/* If we added chline to the line buffer, reset the original contents. */
if (ol) {
cs -= chl;
wb -= chl;
we -= chl;
if (wb < 0) {
strcpy((char *) line, ol);
ll = strlen((char *) line);
cs = ocs;
unmetafy_line();
feep();
return;
}
ocs = cs;
cs = 0;
foredel(chl);
cs = ocs;
}
freeheap();
/* Save the lexer state, in case the completion code uses the lexer *
* somewhere (e.g. when processing a compctl -s flag). */
lexsave();
if (inwhat == IN_ENV)
lincmd = 0;
if (s) {
if (lst == COMP_EXPAND_COMPLETE) {
/* Check if we have to do expansion or completion. */
char *q = s;
if (*q == Equals) {
/* The word starts with `=', see if we can expand it. */
q = s + 1;
if (cmdnamtab->getnode(cmdnamtab, q) || hashcmd(q, pathchecked))
if (isset(RECEXACT))
lst = COMP_EXPAND;
else {
int t0, n = 0;
char *fc;
struct hashnode *hn;
for (t0 = cmdnamtab->hsize - 1; t0 >= 0; t0--)
for (hn = cmdnamtab->nodes[t0]; hn;
hn = hn->next) {
if (strpfx(q, hn->nam) && (fc = findcmd(hn->nam))) {
zsfree(fc);
n++;
}
if (n == 2)
break;
}
if (n == 1)
lst = COMP_EXPAND;
}
}
if (lst == COMP_EXPAND_COMPLETE)
do {
/* check if there is a parameter expresiion. */
for (; *q && *q != String; q++);
if (*q == String && q[1] != Inpar && q[1] != Inbrack) {
if (*++q == Inbrace) {
if (! skipparens(Inbrace, Outbrace, &q) &&
q == s + cs - wb)
lst = COMP_EXPAND;
} else {
char *t, sav, sav2;
/* Skip the things parameter expressions might *
* start with (the things before the parameter *
* name). */
for (; *q; q++)
if (*q != '^' && *q != Hat &&
*q != '=' && *q != Equals &&
*q != '~' && *q != Tilde)
break;
if ((*q == '#' || *q == Pound || *q == '+') &&
q[1] != String)
q++;
sav2 = *(t = q);
if (*q == Quest || *q == Star || *q == String ||
*q == Qstring)
*q = ztokens[*q - Pound], ++q;
else if (*q == '?' || *q == '*' || *q == '$' ||
*q == '-' || *q == '!' || *q == '@')
q++;
else if (idigit(*q))
do q++; while (idigit(*q));
else
while (iident(*q))
q++;
sav = *q;
*q = '\0';
if (cs - wb == q - s &&
(idigit(sav2) || checkparams(t)))
lst = COMP_EXPAND;
*q = sav;
*t = sav2;
}
if (lst != COMP_EXPAND)
lst = COMP_COMPLETE;
} else
break;
} while (q < s + cs - wb);
if (lst == COMP_EXPAND_COMPLETE) {
/* If it is still not clear if we should use expansion or *
* completion and there is a `$' or a backtick in the word, *
* than do expansion. */
for (q = s; *q; q++)
if (*q == Tick || *q == Qtick ||
*q == String || *q == Qstring)
break;
lst = *q ? COMP_EXPAND : COMP_COMPLETE;
}
/* And do expansion if there are wildcards and globcomplete is *
* not used. */
if (unset(GLOBCOMPLETE) && cmphaswilds(s))
lst = COMP_EXPAND;
}
if (lincmd && (inwhat == IN_NOTHING))
inwhat = IN_CMD;
if (lst == COMP_SPELL) {
char **x = &s;
char *q = s;
for (; *q; q++)
if (INULL(*q))
*q = Nularg;
untokenize(s);
cs = wb;
foredel(we - wb);
/* call the real spell checker, ash@aaii.oz.zu */
spckword(x, 0, lincmd, 0);
inststr(*x);
} else if (COMP_ISEXPAND(lst)) {
/* Do expansion. */
char *ol = (olst == COMP_EXPAND_COMPLETE) ?
dupstring((char *)line) : (char *)line;
int ocs = cs, ne = noerrs;
noerrs = 1;
doexpansion(s, lst, olst, lincmd);
lastambig = 0;
noerrs = ne;
/* If expandorcomplete was invoked and the expansion didn't *
* change the command line, do completion. */
if (olst == COMP_EXPAND_COMPLETE &&
!strcmp(ol, (char *)line)) {
char *p;
cs = ocs;
errflag = 0;
p = s;
if (*p == Tilde || *p == Equals)
p++;
for (; *p; p++)
if (itok(*p))
if (*p != String && *p != Qstring)
*p = ztokens[*p - Pound];
else if (p[1] == Inbrace)
p++, skipparens(Inbrace, Outbrace, &p);
docompletion(s, lst, lincmd);
}
} else
/* Just do completion. */
docompletion(s, lst, lincmd);
zsfree(s);
}
/* Reset the lexer state, pop the heap. */
lexrestore();
popheap();
zsfree(qword);
menuce = complexpect;
unmetafy_line();
}
/* Do completion, given that we are in the middle of a menu completion. We *
* don't need to generate a list of matches, because that's already been *
* done by previous commands. We will either list the completions, or *
* insert the next completion. */
/**/
void
do_menucmp(int lst)
{
/* Just list the matches if the list was requested. */
if (lst == COMP_LIST_COMPLETE) {
showinglist = -2;
return;
}
/* Otherwise go to the next match in the array... */
HEAPALLOC {
if (!*++menucur)
menucur = amatches;
complexpect = menuce;
/* ... and insert it into the command line. */
metafy_line();
do_single(*menucur);
unmetafy_line();
} LASTALLOC;
}
/* 1 if x added to complete in a blank between words */
int addedx;
/* 1 if we are completing in a string */
int instring;
/* This function inserts an `x' in the command line at the cursor position. *
* *
* Oh, you want to know why? Well, if completion is tried somewhere on an *
* empty part of the command line, the lexer code would normally not be *
* able to give us the `word' we want to complete, since there is no word. *
* But we need to call the lexer to find out where we are (and for which *
* command we are completing and such things). So we temporarily add a `x' *
* (any character without special meaning would do the job) at the cursor *
* position, than the lexer gives us the word `x' and its beginning and end *
* positions and we can remove the `x'. */
/**/
void
addx(char **ptmp)
{
if (!line[cs] || line[cs] == '\n' ||
(iblank(line[cs]) && (!cs || line[cs-1] != '\\')) ||
line[cs] == ')' || line[cs] == '`') {
*ptmp = (char *)line;
line = (unsigned char *)halloc(strlen((char *)line) + 3);
memcpy(line, *ptmp, cs);
line[cs] = 'x';
strcpy((char *)line + cs + 1, (*ptmp) + cs);
addedx = 1;
} else {
addedx = 0;
*ptmp = NULL;
}
}
/* Like dupstring, but add an extra space at the end of the string. */
/**/
char *
dupstrspace(const char *str)
{
int len = strlen((char *)str);
char *t = (char *)ncalloc(len + 2);
strcpy(t, str);
strcpy(t+len, " ");
return t;
}
/* These functions metafy and unmetafy the ZLE buffer, as described at the *
* top of this file. Note that ll and cs are translated. They *must* be *
* called in matching pairs, around all the expansion/completion code. *
* Currently, there are four pairs: in history expansion, in the main *
* completion function, and one in each of the middle-of-menu-completion *
* functions (there's one for each direction). */
/**/
void
metafy_line(void)
{
int len = ll;
char *s;
for (s = (char *) line; s < (char *) line + ll;)
if (imeta(*s++))
len++;
sizeline(len);
(void) metafy((char *) line, ll, META_NOALLOC);
ll = len;
cs = metalen((char *) line, cs);
}
/**/
void
unmetafy_line(void)
{
cs = ztrsub((char *) line + cs, (char *) line);
(void) unmetafy((char *) line, &ll);
}
/* Lasciate ogni speranza. *
* This function is a nightmare. It works, but I'm sure that nobody really *
* understands why. The problem is: to make it cleaner we would need *
* changes in the lexer code (and then in the parser, and then...). */
/**/
char *
get_comp_string(void)
{
int t0, tt0, i, j, k, cp, rd, sl, ocs;
char *s = NULL, *linptr, *tmp, *p, *tt = NULL, *q = NULL;
/* This global flag is used to signal the lexer code if it should *
* expand aliases or not. */
noaliases = isset(COMPLETEALIASES);
instring = 0;
/* Find out if we are somewhere in a `string', i.e. inside '...', *
* "...", `...`, or ((...)). */
for (i = j = k = 0, q = p = (char *)line; p < (char *)line + cs; p++)
if (*p == '`' && !(k & 1))
i++, q = p;
else if (*p == '\"' && !(k & 1) && !(i & 1))
j++;
else if (*p == '\'' && !(j & 1))
k++;
else if (*p == '\\' && p[1] && !(k & 1))
p++;
if ((i & 1) || (j & 1) || (k & 1)) {
/* Yes, we are in a string. */
instring = (j & 1) ? 2 : (k & 1);
/* Now add the `x', see above. */
addx(&tmp);
if (!addedx) {
/* If no `x' was added, set the tmp variable anyway. */
tmp = (char *)line;
if (i & 1) {
line = (unsigned char *)dupstring((char *)line);
strcpy((char *)line, (char *)tmp);
} else
line = (unsigned char *)dupstring((char *)line);
}
/* Now remove the quotes. *
* What?? Why that?? Well, we want to be able to complete *
* inside strings. The lexer code gives us no help here, *
* so we have to cheat. We remove the quotes, the lexer *
* will than treat the words in the strings normally and we *
* can complete them. *
* This is completely the wrong thing to do, but it's *
* occasionally useful, and we can't handle quotes properly *
* yet anyway. */
for (p = (char *)line; *p; p++)
if (*p == '"' || *p == '\'')
*p = ' ';
} else
/* We are not in a string, add the `x' */
addx(&tmp);
linptr = (char *)line;
pushheap();
HEAPALLOC {
start:
inwhat = IN_NOTHING;
/* Now set up the lexer and start it. */
parbegin = parend = -1;
lincmd = incmdpos;
linredir = inredir;
zsfree(cmdstr);
cmdstr = NULL;
zleparse = 1;
clwpos = -1;
lexsave();
inpush(dupstrspace((char *) linptr), 0);
strinbeg();
stophist = 2;
i = tt0 = cp = rd = 0;
/* This loop is possibly the wrong way to do this. It goes through *
* the previously massaged command line using the lexer. It stores *
* each token in each command (commands being regarded, roughly, as *
* being separated by tokens | & &! |& || &&). The loop stops when *
* the end of the command containing the cursor is reached. It's a *
* simple way to do things, but suffers from an inability to *
* distinguish actual command arguments from, for example, *
* filenames in redirections. (But note that code elsewhere checks *
* if we are completing *in* a redirection.) The only way to fix *
* this would be to pass the command line through the parser too, *
* and get the arguments that way. Maybe in 3.1... */
do {
lincmd = incmdpos;
linredir = inredir;
/* Get the next token. */
ctxtlex();
/* We reached the end. */
if (tok == ENDINPUT)
break;
if (tok == BAR || tok == AMPER ||
tok == BARAMP || tok == AMPERBANG ||
((tok == DBAR || tok == DAMPER) && !incond)) {
/* This is one of the things that separate commands. If we *
* already have the things we need (e.g. the token strings), *
* leave the loop. */
if (tt)
break;
/* Otherwise reset the variables we are collecting data in. */
i = tt0 = cp = rd = 0;
}
if (lincmd && tok == STRING) {
/* The lexer says, this token is in command position, so *
* store the token string (to find the right compctl). */
zsfree(cmdstr);
cmdstr = ztrdup(tokstr);
i = 0;
}
if (!zleparse && !tt0) {
/* This is done when the lexer reached the word the cursor is on. */
tt = tokstr ? dupstring(tokstr) : NULL;
/* If we added a `x', remove it. */
if (addedx && tt)
chuck(tt + cs - wb - 1);
tt0 = tok;
/* Store the number of this word. */
clwpos = i;
cp = lincmd;
rd = linredir;
if (inwhat == IN_NOTHING && incond)
inwhat = IN_COND;
}
if (!tokstr)
continue;
/* We need to store the token strings of all words (for some of *
* the more complicated compctl -x things). They are stored in *
* the clwords array. Make this array big enough. */
if (i + 1 == clwsize) {
int n;
clwords = (char **)realloc(clwords,
(clwsize *= 2) * sizeof(char *));
for(n = clwsize; --n > i; )
clwords[n] = NULL;
}
zsfree(clwords[i]);
/* And store the current token string. */
clwords[i] = ztrdup(tokstr);
sl = strlen(tokstr);
/* Sometimes the lexer gives us token strings ending with *
* spaces we delete the spaces. */
while (sl && clwords[i][sl - 1] == ' ' &&
(sl < 2 || (clwords[i][sl - 2] != Bnull &&
clwords[i][sl - 2] != Meta)))
clwords[i][--sl] = '\0';
/* If this is the word the cursor is in and we added a `x', *
* remove it. */
if (clwpos == i++ && addedx)
chuck(&clwords[i - 1][((cs - wb - 1) >= sl) ?
(sl - 1) : (cs - wb - 1)]);
} while (tok != LEXERR && tok != ENDINPUT &&
(tok != SEPER || (zleparse && !tt0)));
/* Calculate the number of words stored in the clwords array. */
clwnum = (tt || !i) ? i : i - 1;
zsfree(clwords[clwnum]);
clwords[clwnum] = NULL;
t0 = tt0;
lincmd = cp;
linredir = rd;
strinend();
inpop();
errflag = zleparse = 0;
if (addedx)
wb++;
if (parbegin != -1) {
/* We are in command or process substitution */
if (parend >= 0 && !tmp)
line = (unsigned char *) dupstring(tmp = (char *)line);
linptr = (char *) line + ll + addedx - parbegin + 1;
if (parend >= 0) {
ll -= parend;
line[ll + addedx] = '\0';
}
lexrestore();
goto start;
}
if (inwhat == IN_MATH)
s = NULL;
else if (!t0 || t0 == ENDINPUT) {
/* There was no word (empty line). */
s = ztrdup("");
we = wb = cs;
clwpos = clwnum;
t0 = STRING;
} else if (t0 == STRING) {
/* We found a simple string. */
s = ztrdup(clwords[clwpos]);
} else if (t0 == ENVSTRING) {
/* The cursor was inside a parameter assignment. */
for (s = tt; iident(*s); s++);
if (skipparens(Inbrack, Outbrack, &s) > 0 || s > tt + cs - wb)
s = NULL, inwhat = IN_MATH;
else if (*s == '=') {
s++;
wb += s - tt;
t0 = STRING;
s = ztrdup(s);
inwhat = IN_ENV;
}
lincmd = 1;
}
if (we > ll)
we = ll;
tt = (char *)line;
if (tmp) {
line = (unsigned char *)tmp;
ll = strlen((char *)line);
}
if (t0 != STRING && inwhat != IN_MATH) {
if (tmp) {
tmp = NULL;
linptr = (char *)line;
lexrestore();
goto start;
}
feep();
noaliases = 0;
lexrestore();
LASTALLOC_RETURN NULL;
}
noaliases = 0;
/* Check if we are in an array subscript. We simply assume that *
* we are in a subscript if we are in brackets. Correct solution *
* is very difficult. This is quite close, but gets things like *
* foo[_ wrong (note no $). If we are in a subscript, treat it *
* as being in math. */
if (inwhat != IN_MATH) {
int i = 0;
for (tt = s; ++tt < s + cs - wb;)
if (*tt == Inbrack)
i++;
else if (i && *tt == Outbrack)
i--;
if (i)
inwhat = IN_MATH;
}
if (inwhat == IN_MATH) {
/* In mathematical expression, we complete parameter names (even *
* if they don't have a `$' in front of them). So we have to *
* find that name. */
for (we = cs; iident(line[we]); we++);
for (wb = cs; --wb >= 0 && iident(line[wb]););
wb++;
zsfree(s);
s = zalloc(we - wb + 1);
strncpy(s, (char *) line + wb, we - wb);
s[we - wb] = '\0';
}
/* This variable will hold the current word in quoted form. */
qword = ztrdup(s);
/* While building the quoted form, we also clean up the command line. */
offs = cs - wb;
for (p = s, tt = qword, i = wb; *p; p++, tt++, i++)
if (INULL(*p)) {
if (i < cs)
offs--;
if (p[1] || *p != Bnull) {
if (*p == Bnull) {
*tt = '\\';
if (cs == i + 1)
cs++, offs++;
} else {
ocs = cs;
cs = i;
foredel(1);
chuck(tt--);
if ((cs = ocs) >= i--)
cs--;
we--;
}
} else {
ocs = cs;
*tt = '\0';
cs = we;
backdel(1);
if (ocs == we)
cs = we - 1;
else
cs = ocs;
we--;
}
chuck(p--);
}
} LASTALLOC;
lexrestore();
return (char *)s;
}
/* Expand the current word. */
/**/
void
doexpansion(char *s, int lst, int olst, int explincmd)
{
LinkList vl;
char *ss;
DPUTS(useheap, "BUG: useheap in doexpansion()");
HEAPALLOC {
pushheap();
vl = newlinklist();
ss = dupstring(s);
addlinknode(vl, ss);
prefork(vl, 0);
if (errflag)
goto end;
if ((lst == COMP_LIST_EXPAND) || (lst == COMP_EXPAND)) {
int ng = opts[NULLGLOB];
opts[NULLGLOB] = 1;
globlist(vl);
opts[NULLGLOB] = ng;
}
if (errflag)
goto end;
if (empty(vl) || !*(char *)peekfirst(vl)) {
if (!noerrs)
feep();
goto end;
}
if (peekfirst(vl) == (void *) ss ||
(olst == COMP_EXPAND_COMPLETE &&
!nextnode(firstnode(vl)) && *s == Tilde &&
(ss = dupstring(s), filesubstr(&ss, 0)) &&
!strcmp(ss, (char *)peekfirst(vl)))) {
/* If expansion didn't change the word, try completion if *
* expandorcomplete was called, otherwise, just beep. */
if (lst == COMP_EXPAND_COMPLETE)
docompletion(s, COMP_COMPLETE, explincmd);
else
feep();
goto end;
}
if (lst == COMP_LIST_EXPAND) {
/* Only the list of expansions was requested. */
listlist(vl);
goto end;
}
/* Remove the current word and put the expansions there. */
cs = wb;
foredel(we - wb);
while ((ss = (char *)ugetnode(vl))) {
untokenize(ss);
ss = quotename(ss, NULL, NULL, NULL);
inststr(ss);
#if 0
if (nonempty(vl)) {
spaceinline(1);
line[cs++] = ' ';
}
#endif
if (olst != COMP_EXPAND_COMPLETE || nonempty(vl) ||
(cs && line[cs-1] != '/')) {
spaceinline(1);
line[cs++] = ' ';
}
}
end:
popheap();
} LASTALLOC;
}
/* This is called from the lexer to give us word positions. */
/**/
void
gotword(void)
{
we = ll + 1 - inbufct;
if (cs <= we) {
wb = ll - wordbeg;
zleparse = 0;
}
}
/* Insert the given string into the command line. If move is non-zero, *
* the cursor position is changed and len is the length of the string *
* to insert (if it is -1, the length is calculated here). */
/**/
void
inststrlen(char *str, int move, int len)
{
if (!len)
return;
if (len == -1)
len = strlen(str);
spaceinline(len);
strncpy((char *)(line + cs), str, len);
if (remove_at >= cs)
remove_at += len;
if (move)
cs += len;
}
/* Quote the string s and return the result. If e is non-zero, it the *
* pointer it points to may point to aposition in s and in e the position *
* of the corresponding character in the quoted string is returned. Like *
* e, te may point to a position in the string and pl is used to return *
* the position of the character pointed to by te in the quoted string. *
* The string is metafied and may contain tokens. */
/**/
char *
quotename(char *s, char **e, char *te, int *pl)
{
char *tt, *v, *u, buf[PATH_MAX * 2];
int sf = 0;
tt = v = buf;
u = s;
for (; *u; u++) {
if (e && *e == u)
*e = v, sf |= 1;
if (te == u)
*pl = v - tt, sf |= 2;
if (ispecial(*u) &&
(!instring || (!isset(NOBANGHIST) &&
*u == (char)bangchar) ||
(instring == 2 &&
(*u == '$' || *u == '`' || *u == '\"')) ||
(instring == 1 && *u == '\'')))
if (*u == '\n' || (instring == 1 && *u == '\'')) {
if (unset(RCQUOTES)) {
*v++ = '\'';
if (*u == '\'')
*v++ = '\\';
*v++ = *u;
*v++ = '\'';
} else if (*u == '\n')
*v++ = '"', *v++ = '\n', *v++ = '"';
else
*v++ = '\'', *v++ = '\'';
continue;
} else
*v++ = '\\';
if(*u == Meta)
*v++ = *u++;
*v++ = *u;
}
*v = '\0';
if (strcmp(buf, s))
tt = dupstring(buf);
else
tt = s;
v += tt - buf;
if (e && (sf & 1))
*e += tt - buf;
if (e && *e == u)
*e = v;
if (te == u)
*pl = v - tt;
return tt;
}
/* This adds a match to the list of matches. The string to add is given *
* in s, the type of match is given in the global variable addwhat and *
* the parameter t (if not NULL) is a pointer to a hash node node which *
* may be used to give other information to this function. *
* *
* addwhat contains either one of the special values (negative, see below) *
* or the inclusive OR of some of the CC_* flags used for compctls. */
/**/
void
addmatch(char *s, char *t)
{
int test = 0, sl = strlen(s), pl = rpl, cc = 0, *bp, *ep, *sp;
char *e = NULL, *tt, *te, *fc, **fm;
Comp cp = patcomp;
HashNode hn;
Param pm;
LinkList l = matches;
/*
* addwhat: -5 is for files,
* -6 is for glob expansions,
* -8 is for executable files (e.g. command paths),
* -7 is for command names (from cmdnamtab)
* -3 is for executable command names.
* -1 is for other file specifications
* (things with `~' of `=' at the beginning, ...).
*/
/* Just to make the code cleaner */
hn = (HashNode) t;
pm = (Param) t;
if (!addwhat) {
test = 1;
} else if (addwhat == -1 || addwhat == -5 || addwhat == -6 ||
addwhat == CC_FILES || addwhat == -7 || addwhat == -8) {
if (sl < fpl + fsl)
return;
if ((addwhat == CC_FILES ||
addwhat == -5) && !*psuf && !*fsuf) {
/* If this is a filename, do the fignore check. */
char **pt = fignore;
int filell;
for (test = 1; test && *pt; pt++)
if ((filell = strlen(*pt)) < sl
&& !strcmp(*pt, s + sl - filell))
test = 0;
if (!test)
l = fmatches;
}
pl = fpl;
if (addwhat == -5 || addwhat == -8) {
test = 1;
cp = filecomp;
cc = cp || ispattern;
e = s + sl - fsl;
} else {
if ((cp = filecomp)) {
if ((test = domatch(s, filecomp, 0)))
cc = 1;
} else {
e = s + sl - fsl;
if ((test = !strncmp(s, fpre, fpl)))
test = !strcmp(e, fsuf);
if (ispattern)
cc = 1;
}
}
if (test) {
fc = NULL;
if (addwhat == -7 && !(fc = findcmd(s)))
return;
if (fc)
zsfree(fc);
haswhat |= HAS_FILES;
if (addwhat == CC_FILES || addwhat == -6 ||
addwhat == -5 || addwhat == -8) {
te = s + pl;
s = quotename(s, &e, te, &pl);
sl = strlen(s);
} else if (!cc) {
s = dupstring(t = s);
e += s - t;
}
if (cc) {
tt = (char *)halloc(strlen(ppre) + strlen(psuf) + sl + 1);
strcpy(tt, ppre);
strcat(tt, s);
strcat(tt, psuf);
untokenize(s = tt);
}
}
} else if (addwhat == CC_QUOTEFLAG || addwhat == -2 ||
(addwhat == -3 && !(hn->flags & DISABLED)) ||
(addwhat == -4 && (PM_TYPE(pm->flags) == PM_SCALAR) &&
(tt = pm->gets.cfn(pm)) && *tt == '/') ||
(addwhat > 0 &&
(((addwhat & CC_ARRAYS) && (hn->flags & PM_ARRAY)) ||
((addwhat & CC_INTVARS) && (hn->flags & PM_INTEGER)) ||
((addwhat & CC_ENVVARS) && (hn->flags & PM_EXPORTED)) ||
((addwhat & CC_SCALARS) && (hn->flags & PM_SCALAR)) ||
((addwhat & CC_READONLYS) && (hn->flags & PM_READONLY)) ||
((addwhat & CC_SPECIALS) && (hn->flags & PM_SPECIAL)) ||
((addwhat & CC_PARAMS) && !(hn->flags & PM_EXPORTED)) ||
(((addwhat & CC_SHFUNCS) ||
(addwhat & CC_BUILTINS) ||
(addwhat & CC_EXTCMDS) ||
(addwhat & CC_RESWDS) ||
((addwhat & CC_ALREG) && !(hn->flags & ALIAS_GLOBAL)) ||
((addwhat & CC_ALGLOB) && (hn->flags & ALIAS_GLOBAL))) &&
(((addwhat & CC_DISCMDS) && (hn->flags & DISABLED)) ||
((addwhat & CC_EXCMDS) && !(hn->flags & DISABLED))))))) {
if (sl >= rpl + rsl) {
if (cp)
test = domatch(s, patcomp, 0);
else {
e = s + sl - rsl;
if ((test = !strncmp(s, rpre, rpl)))
test = !strcmp(e, rsuf);
}
}
if (!test && sl < lpl + lsl)
return;
if (!test && lpre && lsuf && sl >= lpl + lsl) {
e = s + sl - lsl;
if ((test = !strncmp(s, lpre, lpl)))
test = !strcmp(e, lsuf);
pl = lpl;
}
if (addwhat == CC_QUOTEFLAG) {
te = s + pl;
s = quotename(s, &e, te, &pl);
sl = strlen(s);
}
if (test)
haswhat |= HAS_MISC;
}
if (!test)
return;
if (ispattern) {
t = s;
} else {
t = s += pl;
if (*e) {
sl = e - s;
t = s = dupstring(t);
s[sl] = '\0';
}
}
if (l == fmatches) {
bp = &fab;
ep = &fae;
sp = &fshortl;
fm = &ffirstm;
} else {
bp = &ab;
ep = &ae;
sp = &shortl;
fm = &firstm;
}
if (!ispattern && *fm) {
if ((test = pfxlen(*fm, s)) < *bp)
*bp = test;
if ((test = sfxlen(*fm, s)) < *ep)
*ep = test;
}
/* If we are doing a glob completion we store the whole string in *
* the list. Otherwise only the part that fits between the prefix *
* and the suffix is stored. */
addlinknode(l, t);
if (!*fm) {
*bp = *ep = 10000;
*fm = t;
*sp = 100000;
}
if (!ispattern && (sl = strlen(t)) < *sp) {
*sp = sl;
if (l == fmatches)
fshortest = t;
else
shortest = t;
}
}
#ifdef HAVE_NIS_PLUS
static int
match_username(nis_name table, nis_object *object, void *userdata)
{
if (errflag)
return 1;
else {
static char buf[40];
register entry_col *ec =
object->zo_data.objdata_u.en_data.en_cols.en_cols_val;
register int l = minimum(ec->ec_value.ec_value_len, 39);
memcpy(buf, ec->ec_value.ec_value_val, l);
buf[l] = '\0';
addmatch(dupstring(buf), NULL);
}
return 0;
}
#else
# ifdef HAVE_NIS
static int
match_username(int status, char *key, int keylen, char *val, int vallen, dopestring *data)
{
if (errflag || status != YP_TRUE)
return 1;
if (vallen > keylen && val[keylen] == ':') {
val[keylen] = '\0';
addmatch(dupstring(val), NULL);
}
return 0;
}
# endif /* HAVE_NIS */
#endif /* HAVE_NIS_PLUS */
/**/
void
maketildelist(void)
{
#if defined(HAVE_NIS) || defined(HAVE_NIS_PLUS)
FILE *pwf;
char buf[BUFSIZ], *p;
int skipping;
# ifndef HAVE_NIS_PLUS
char domain[YPMAXDOMAIN];
struct ypall_callback cb;
dopestring data;
data.s = fpre;
data.len = fpl;
/* Get potential matches from NIS and cull those without local accounts */
if (getdomainname(domain, YPMAXDOMAIN) == 0) {
cb.foreach = (int ((*)()))match_username;
cb.data = (char *)&data;
yp_all(domain, PASSWD_MAP, &cb);
/* for (n = firstnode(matches); n; incnode(n))
if (getpwnam(getdata(n)) == NULL)
uremnode(matches, n);*/
}
# else /* HAVE_NIS_PLUS */
/* Maybe we should turn this string into a #define'd constant...? */
nis_list("passwd.org_dir", EXPAND_NAME|ALL_RESULTS|FOLLOW_LINKS|FOLLOW_PATH,
match_username, 0);
# endif
/* Don't forget the non-NIS matches from the flat passwd file */
if ((pwf = fopen(PASSWD_FILE, "r")) != NULL) {
skipping = 0;
while (fgets(buf, BUFSIZ, pwf) != NULL) {
if (strchr(buf, '\n') != NULL) {
if (!skipping) {
if ((p = strchr(buf, ':')) != NULL) {
*p = '\0';
addmatch(dupstring(buf), NULL);
}
} else
skipping = 0;
} else
skipping = 1;
}
fclose(pwf);
}
#else /* no NIS or NIS_PLUS */
/* add all the usernames to the named directory table */
nameddirtab->filltable(nameddirtab);
#endif
scanhashtable(nameddirtab, 0, (addwhat==-1) ? 0 : ND_USERNAME, 0,
addhnmatch, 0);
}
/* Copy the given string and remove backslashes from the copy and return it. */
/**/
char *
rembslash(char *s)
{
char *t = s = dupstring(s);
while (*s)
if (*s == '\\') {
chuck(s);
if (*s)
s++;
} else
s++;
return t;
}
/* This does the check for compctl -x `n' and `N' patterns. */
/**/
int
getcpat(char *wrd, int cpatindex, char *cpat, int class)
{
char *str, *s, *t, *p;
int d = 0;
if (!wrd || !*wrd)
return -1;
cpat = rembslash(cpat);
str = ztrdup(wrd);
untokenize(str);
if (!cpatindex)
cpatindex++, d = 0;
else if ((d = (cpatindex < 0)))
cpatindex = -cpatindex;
for (s = d ? str + strlen(str) - 1 : str;
d ? (s >= str) : *s;
d ? s-- : s++) {
for (t = s, p = cpat; *t && *p; p++) {
if (class) {
if (*p == *s && !--cpatindex) {
zsfree(str);
return (int)(s - str + 1);
}
} else if (*t++ != *p)
break;
}
if (!class && !*p && !--cpatindex) {
zsfree(str);
t += wrd - str;
for (d = 0; --t >= wrd;)
if (! INULL(*t))
d++;
return d;
}
}
zsfree(str);
return -1;
}
/* This holds a pointer to the compctl we are using. */
Compctl ccmain;
/* Find the compctl to use and return it. The first argument gives a *
* compctl to start searching with (if it is zero, the hash table is *
* searched). compadd is used to return a number of characters that *
* should be ignored at the beginning of the word and incmd is *
* non-zero if we are in command position. */
/**/
Compctl
get_ccompctl(Compctl occ, int *compadd, int incmd)
{
Compctl compc, ret;
Compctlp ccp;
int t, i, a, b, tt, ra, rb, j, isf = 1;
Compcond or, cc;
char *s, *ss, *sc, *cmd = dupstring(cmdstr);
Comp comp;
first_rec:
*compadd = 0;
ra = 0;
rb = clwnum - 1;
sc = NULL;
if (!(ret = compc = occ)) {
if (isf) {
isf = 0;
ret = &cc_first;
}
else if (inwhat == IN_ENV)
/* Default completion for parameter values. */
ret = &cc_default;
else if (inwhat == IN_MATH) {
/* Parameter names inside mathematical expression. */
cc_dummy.mask = CC_PARAMS;
ret = &cc_dummy;
cc_dummy.refc = 10000;
} else if (inwhat == IN_COND) {
/* We try to be clever here: in conditions we complete option *
* names after a `-o', file names after `-nt', `-ot', and `-ef' *
* and file names and parameter names elsewhere. */
s = clwpos ? clwords[clwpos - 1] : "";
cc_dummy.mask = !strcmp("-o", s) ? CC_OPTIONS :
((*s == '-' && s[1] && !s[2]) ||
!strcmp("-nt", s) ||
!strcmp("-ot", s) ||
!strcmp("-ef", s)) ? CC_FILES :
(CC_FILES | CC_PARAMS);
ret = &cc_dummy;
cc_dummy.refc = 10000;
} else if (incmd)
ret = &cc_compos;
/* And in redirections or if there is no command name (and we are *
* not in command position) or if no special compctl was given *
* for the command: use default completion. Note that we first *
* search the complete command name and than the trailing *
* pathname component. */
else if (linredir ||
!(cmd &&
(((ccp = (Compctlp) compctltab->getnode(compctltab, cmd)) &&
(compc = ret = ccp->cc)) ||
((s = dupstring(cmd)) && remlpaths(&s) &&
(ccp = (Compctlp) compctltab->getnode(compctltab, s)) &&
(compc = ret = ccp->cc)))))
ret = &cc_default;
ccmain = compc = ret;
ccmain->refc++;
}
/* The compctl we found has extended completion patterns, check them. */
if (compc && compc->ext) {
compc = compc->ext;
/* This loops over the patterns separated by `--'. */
for (t = 0; compc && !t; compc = compc->next) {
/* This loops over OR'ed patterns. */
for (cc = compc->cond; cc && !t; cc = or) {
or = cc->or;
/* This loops over AND'ed patterns. */
for (t = 1; cc && t; cc = cc->and) {
/* And this loops of [...] pairs. */
for (t = i = 0; i < cc->n && !t; i++) {
s = NULL;
ra = 0;
rb = clwnum - 1;
switch (cc->type) {
case CCT_POS:
tt = clwpos;
goto cct_num;
case CCT_NUMWORDS:
tt = clwnum;
cct_num:
if ((a = cc->u.r.a[i]) < 0)
a += clwnum;
if ((b = cc->u.r.b[i]) < 0)
b += clwnum;
if (cc->type == CCT_POS)
ra = a, rb = b;
t = (tt >= a && tt <= b);
break;
case CCT_CURSUF:
case CCT_CURPRE:
s = ztrdup(clwpos < clwnum ? clwords[clwpos] : "");
untokenize(s);
sc = rembslash(cc->u.s.s[i]);
a = strlen(sc);
if (!strncmp(s, sc, a)) {
*compadd = (cc->type == CCT_CURSUF ? a : 0);
t = 1;
}
break;
case CCT_CURSUB:
case CCT_CURSUBC:
if (clwpos < 0 || clwpos > clwnum)
t = 0;
else {
a = getcpat(clwords[clwpos],
cc->u.s.p[i],
cc->u.s.s[i],
cc->type == CCT_CURSUBC);
if (a != -1)
*compadd = a, t = 1;
}
break;
case CCT_CURPAT:
case CCT_CURSTR:
tt = clwpos;
goto cct_str;
case CCT_WORDPAT:
case CCT_WORDSTR:
tt = 0;
cct_str:
if ((a = tt + cc->u.s.p[i]) < 0)
a += clwnum;
s = ztrdup((a < 0 || a >= clwnum) ? "" :
clwords[a]);
untokenize(s);
if (cc->type == CCT_CURPAT ||
cc->type == CCT_WORDPAT) {
tokenize(ss = dupstring(cc->u.s.s[i]));
t = ((comp = parsereg(ss)) &&
domatch(s, comp, 0));
} else
t = (!strcmp(s, rembslash(cc->u.s.s[i])));
break;
case CCT_RANGESTR:
case CCT_RANGEPAT:
if (cc->type == CCT_RANGEPAT)
tokenize(sc = dupstring(cc->u.l.a[i]));
for (j = clwpos; j; j--) {
untokenize(s = ztrdup(clwords[j]));
if (cc->type == CCT_RANGESTR)
sc = rembslash(cc->u.l.a[i]);
if (cc->type == CCT_RANGESTR ?
!strncmp(s, sc, strlen(sc)) :
((comp = parsereg(sc)) &&
domatch(s, comp, 0))) {
zsfree(s);
ra = j + 1;
t = 1;
break;
}
zsfree(s);
}
if (t) {
if (cc->type == CCT_RANGEPAT)
tokenize(sc = dupstring(cc->u.l.b[i]));
for (j++; j < clwnum; j++) {
untokenize(s = ztrdup(clwords[j]));
if (cc->type == CCT_RANGESTR)
sc = rembslash(cc->u.l.b[i]);
if (cc->type == CCT_RANGESTR ?
!strncmp(s, sc, strlen(sc)) :
((comp = parsereg(sc)) &&
domatch(s, comp, 0))) {
zsfree(s);
rb = j - 1;
t = clwpos <= rb;
break;
}
zsfree(s);
}
}
s = NULL;
}
zsfree(s);
}
}
}
if (t)
break;
}
if (compc)
/* We found a matching pattern, we may return it. */
ret = compc;
}
if (ret->subcmd) {
/* The thing we want to return has a subcmd flag (-l). */
char **ow = clwords, *os = cmdstr, *ops = NULL;
int oldn = clwnum, oldp = clwpos;
/* So we restrict the words-array. */
if (ra >= clwnum)
ra = clwnum - 1;
if (ra < 1)
ra = 1;
if (rb >= clwnum)
rb = clwnum - 1;
if (rb < 1)
rb = 1;
clwnum = rb - ra + 1;
clwpos = clwpos - ra;
if (ret->subcmd[0]) {
/* And probably put the command name given to the flag *
* in the array. */
clwpos++;
clwnum++;
incmd = 0;
ops = clwords[ra - 1];
clwords[ra - 1] = cmdstr = ret->subcmd;
clwords += ra - 1;
} else {
cmdstr = clwords[ra];
incmd = !clwpos;
clwords += ra;
}
*compadd = 0;
if (ccmain != &cc_dummy)
freecompctl(ccmain);
/* Then we call this function recursively. */
ret = get_ccompctl(NULL, compadd, incmd);
/* And restore the things we changed. */
clwords = ow;
cmdstr = os;
clwnum = oldn;
clwpos = oldp;
if (ops)
clwords[ra - 1] = ops;
}
if (ret == &cc_first)
goto first_rec;
return ret;
}
/* Dump a hash table (without sorting). For each element the addmatch *
* function is called and at the beginning the addwhat variable is set. *
* This could be done using scanhashtable(), but this is easy and much *
* more efficient. */
/**/
void
dumphashtable(HashTable ht, int what)
{
HashNode hn;
int i;
addwhat = what;
for (i = 0; i < ht->hsize; i++)
for (hn = ht->nodes[i]; hn; hn = hn->next)
addmatch(hn->nam, (char *) hn);
}
/* ScanFunc used by maketildelist() et al. */
/**/
void
addhnmatch(HashNode hn, int flags)
{
addmatch(hn->nam, NULL);
}
/* Perform expansion on the given string and return the result. *
* During this errors are not reported. */
/**/
char *
getreal(char *str)
{
LinkList l = newlinklist();
int ne = noerrs;
noerrs = 1;
addlinknode(l, dupstring(str));
prefork(l, 0);
noerrs = ne;
if (!errflag && nonempty(l))
return ztrdup(peekfirst(l));
errflag = 0;
return ztrdup(str);
}
/* This reads a directory and adds the files to the list of *
* matches. The parameters say which files should be added. */
/**/
void
gen_matches_files(int dirs, int execs, int all)
{
DIR *d;
struct dirent *de;
struct stat buf;
char *n, p[PATH_MAX], *q = NULL, *e;
LinkList l = NULL;
int ns = 0, ng = opts[NULLGLOB], test, aw = addwhat;
addwhat = execs ? -8 : -5;
opts[NULLGLOB] = 1;
if (*psuf) {
/* If there is a path suffix, check if it doesn't have a `*' or *
* `)' at the end (this is used to determine if we should use *
* globbing). */
q = psuf + strlen(psuf) - 1;
ns = !(*q == Star || *q == Outpar);
l = newlinklist();
/* And generate only directory names. */
dirs = 1;
all = execs = 0;
}
/* Open directory. */
if ((d = opendir((prpre && *prpre) ? prpre : "."))) {
/* If we search only special files, prepare a path buffer for stat. */
if (!all && prpre) {
strcpy(p, prpre);
q = p + strlen(prpre);
}
/* Fine, now read the directory. */
while ((de = readdir(d)) && !errflag) {
n = de->d_name;
/* Ignore `.' and `..'. */
if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0')))
continue;
/* Ignore files beginning with `.' unless the thing we found on *
* the command line also starts with a dot or GLOBDOTS is set. */
if (*n != '.' || *fpre == '.' || isset(GLOBDOTS)) {
if (filecomp)
/* If we have a pattern for the filename check, use it. */
test = domatch(n, filecomp, 0);
else {
/* Otherwise use the prefix and suffix strings directly. */
e = n + strlen(n) - fsl;
if ((test = !strncmp(n, fpre, fpl)))
test = !strcmp(e, fsuf);
}
/* Filename didn't match? */
if (!test)
continue;
if (!all) {
/* We still have to check the file type, so prepare *
* the path buffer by appending the filename. */
strcpy(q, n);
/* And do the stat. */
if (stat(p, &buf) < 0)
continue;
}
if (all ||
(dirs && (buf.st_mode & S_IFMT) == S_IFDIR) ||
(execs && ((buf.st_mode & (S_IFMT | S_IEXEC))
== (S_IFREG | S_IEXEC)))) {
/* If we want all files or the file has the right type... */
if (*psuf) {
/* We have to test for a path suffix. */
int o = strlen(p), tt;
/* Append it to the path buffer. */
strcpy(p + o, psuf);
/* Do we have to use globbing? */
if (ispattern || (ns && isset(GLOBCOMPLETE))) {
/* Yes, so append a `*' if needed. */
if (ns) {
int tl = strlen(p);
p[tl] = Star;
p[tl + 1] = '\0';
}
/* Do the globbing... */
remnulargs(p);
addlinknode(l, p);
globlist(l);
/* And see if that produced a filename. */
tt = nonempty(l);
while (ugetnode(l));
} else
/* Otherwise just check, if we have access *
* to the file. */
tt = !access(p, F_OK);
p[o] = '\0';
if (tt)
/* Ok, we can add the filename to the *
* list of matches. */
addmatch(dupstring(n), NULL);
} else
/* We want all files, so just add the name *
* to the matches. */
addmatch(dupstring(n), NULL);
}
}
}
closedir(d);
}
opts[NULLGLOB] = ng;
addwhat = aw;
}
/* This holds the explanation string we have to print. */
char *expl;
/* This holds the suffix to add (given with compctl -S). */
char *ccsuffix;
/* This s non-zero if the compctl -q flag was given (the suffix should *
* be removed when a space or something like that is typed next). */
int remsuffix;
/**/
void
quotepresuf(char **ps)
{
if (*ps) {
char *p = quotename(*ps, NULL, NULL, NULL);
if (p != *ps) {
zsfree(*ps);
*ps = ztrdup(p);
}
}
}
/* This is non-zero if the cursor was moved up after showing a list *
* of completions (with alwayslastprompt). */
int clearflag;
/**/
void
docompletion(char *s, int lst, int incmd)
{
static int delit, compadd;
HEAPALLOC {
pushheap();
/* Make sure we have the completion list and compctl. */
if(makecomplist(s, incmd, &delit, &compadd)) {
/* Error condition: feeeeeeeeeeeeep(). */
feep();
goto compend;
}
if (lst == COMP_LIST_COMPLETE)
/* All this and the guy only wants to see the list, sigh. */
showinglist = -2;
else {
/* We have matches. */
if (delit) {
/* If we have to delete the word from the command line, *
* do it now. */
wb -= compadd;
strcpy((char *)line + wb, (char *)line + we);
we = cs = wb;
}
if (nmatches>1)
/* There are more than one match. */
do_ambiguous();
else {
/* Only one match. */
do_single(amatches[0]);
invalidatelist();
}
}
/* Print the explanation string if needed. */
if (!showinglist && expl && nmatches!=1) {
int up;
trashzle();
clearflag = (isset(USEZLE) && termok &&
(isset(ALWAYSLASTPROMPT) && zmult == 1)) ||
(unset(ALWAYSLASTPROMPT) && zmult != 1);
up = printfmt(expl, nmatches, 1);
if (clearflag)
tcmultout(TCUP, TCMULTUP, up + nlnct);
else
putc('\n', shout);
fflush(shout);
}
compend:
ll = strlen((char *)line);
if (cs > ll)
cs = ll;
popheap();
} LASTALLOC;
}
/* Create the completion list. This is called whenever some bit of *
* completion code needs the list. If the list is already available *
* (validlist!=0), this function doesn't do anything. Along with *
* the list is maintained the prefixes/suffixes etc. When any of *
* this becomes invalid -- e.g. if some text is changed on the *
* command line -- invalidatelist() should be called, to set *
* validlist to zero and free up the memory used. This function *
* returns non-zero on error. delit and compadd return information *
* about bits of the command line that need to be deleted. */
/**/
int
makecomplist(char *s, int incmd, int *delit, int *compadd)
{
Compctl cc = NULL;
int oloffs = offs, owe = we, owb = wb, ocs = cs, isf = 1;
int t, sf1, sf2, ooffs;
char *p, *sd = NULL, sav, *tt, *s1, *s2, *os = NULL;
unsigned char *ol = NULL;
/* If we already have a list from a previous execution of this *
* function, skip the list building code. */
if (validlist)
return !nmatches;
os = dupstring(s);
ol = (unsigned char *)dupstring((char *)line);
xorrec:
/* Go to the end of the word if complete_in_word is not set. */
if (unset(COMPLETEINWORD) && cs != we)
cs = we, offs = strlen(s);
ispattern = haswhat = lastambig = 0;
patcomp = filecomp = NULL;
menucur = NULL;
shortest = NULL;
fshortest = NULL;
rpre = rsuf = lpre = lsuf = ppre = psuf = prpre =
fpre = fsuf = firstm = ffirstm = parampre = qparampre = NULL;
/* Blank out the lists. */
matches = newlinklist();
fmatches = newlinklist();
/* If we don't have a compctl definition yet or we have a compctl *
* with extended completion, get it (or the next one, resp.). */
if (!cc || cc->ext)
cc = get_ccompctl(cc, compadd, incmd);
/* *compadd is the number of characters we have to ignore at the *
* beginning of the word. */
wb += *compadd;
s += *compadd;
if ((offs -= *compadd) < 0) {
/* It's bigger than our word prefix, so we can't help here... */
feep();
return 1;
}
/* Insert the prefix (compctl -P), if any. */
if (cc->prefix) {
int pl = 0, sl = strlen(cc->prefix);
if (*s) {
/* First find out how much of the prefix is already on the line. */
sd = dupstring(s);
untokenize(sd);
pl = pfxlen(cc->prefix, sd);
s += pl;
}
if (pl < sl) {
int savecs = cs;
/* Then insert the prefix. */
cs = wb + pl;
inststrlen(cc->prefix + pl, 0, sl - pl);
cs = savecs + sl - pl;
}
/* And adjust the word beginning/end variables. */
wb += sl;
we += sl - pl;
offs -= pl;
}
/* Does this compctl have a suffix (compctl -S)? */
if ((ccsuffix = cc->suffix) && *ccsuffix) {
char *sdup = dupstring(ccsuffix);
int sl = strlen(sdup), suffixll;
/* Ignore trailing spaces. */
for (p = sdup + sl - 1; p >= sdup && *p == ' '; p--, sl--);
p[1] = '\0';
if (!sd) {
sd = dupstring(s);
untokenize(sd);
}
/* If the suffix is already there, ignore it (and don't add *
* it again). */
if (*sd && (suffixll = strlen(sd)) >= sl &&
!strcmp(sdup, sd + suffixll - sl)) {
ccsuffix = NULL;
haswhat |= HAS_SUFFIX;
s[suffixll - sl] = '\0';
}
}
/* Do we have one of the special characters `~' and `=' at the beginning? */
if ((ic = *s) != Tilde && ic != Equals)
ic = 0;
/* Check if we have to complete a parameter name... */
complexpect = menuce = 0;
/* Try to find a `$'. */
for (p = s + offs; p > s && *p != String; p--);
if (*p == String) {
/* Handle $$'s */
while (p > s && p[-1] == String)
p--;
while (p[1] == String && p[2] == String)
p += 2;
}
if (*p == String && p[1] != Inpar && p[1] != Inbrack) {
/* This is really a parameter expression (not $(...) or $[...]). */
char *b = p + 1, *e = b;
int n = 0, br = 1;
if (*b == Inbrace) {
/* If this is a ${...}, ignore the possible (...) flags. */
b++, br++;
n = skipparens(Inpar, Outpar, &b);
}
/* Ignore the stuff before the parameter name. */
for (; *b; b++)
if (*b != '^' && *b != Hat &&
*b != '=' && *b != Equals &&
*b != '~' && *b != Tilde)
break;
if (*b == '#' || *b == Pound || *b == '+')
b++;
e = b;
/* Find the end of the name. */
if (*e == Quest || *e == Star || *e == String || *e == Qstring ||
*e == '?' || *e == '*' || *e == '$' ||
*e == '-' || *e == '!' || *e == '@')
e++;
else if (idigit(*e))
while (idigit(*e))
e++;
else if (iident(*e))
while (iident(*e) ||
(useglob && (*e == Star || *e == Quest)))
e++;
/* Now make sure that the cursor is inside the name. */
if (offs <= e - s && offs >= b - s && n <= 0) {
/* It is, set complexpect. */
if (cs == we || ! *e || *e == ' ')
complexpect = br;
/* Get the prefix (anything up to the character before the name). */
*e = '\0';
sav = *b;
*b = '\0';
parampre = ztrdup(s);
qparampre = ztrdup(quotename(s, NULL, NULL, NULL));
untokenize(qparampre);
qparprelen = strlen(qparampre);
*b = sav;
/* And adjust wb, we, and offs again. */
offs -= b - s;
wb = cs - offs;
we = wb + e - b;
s = b;
/* And now make sure that we complete parameter names. */
cc = ccmain = &cc_dummy;
cc_dummy.refc = 10000;
cc_dummy.mask = CC_PARAMS | CC_ENVVARS;
} else
complexpect = 0;
}
ooffs = offs;
/* If we have to ignore the word, do that. */
if (cc->mask & CC_DELETE) {
*delit = 1;
*s = '\0';
offs = 0;
} else
*delit = 0;
/* Compute line prefix/suffix. */
lpl = offs;
lpre = zalloc(lpl + 1);
memcpy(lpre, s, lpl);
lpre[lpl] = '\0';
p = quotename(lpre, NULL, NULL, NULL);
if (strcmp(p, lpre) && !strpfx(p, qword)) {
int l1, l2;
backdel(l1 = cs - wb);
untokenize(p);
inststrlen(p, 1, l2 = strlen(p));
we += l2 - l1;
}
lsuf = ztrdup(s + offs);
lsl = strlen(lsuf);
if (lsl && (p = quotename(lsuf, NULL, NULL, NULL)) &&
(strcmp(p, lsuf) && !strsfx(p, qword))) {
int l1, l2;
foredel(l1 = strlen(s + offs));
untokenize(p);
inststrlen(p, 0, l2 = strlen(p));
we += l2 - l1;
}
/* First check for ~.../... */
if (ic == Tilde) {
for (p = lpre + lpl; p > lpre; p--)
if (*p == '/')
break;
if (*p == '/')
ic = 0;
}
/* Compute real prefix/suffix. */
noreal = !*delit;
for (p = lpre; *p && *p != String && *p != Tick; p++);
tt = ic && !parampre ? lpre + 1 : lpre;
rpre = (*p || *lpre == Tilde || *lpre == Equals) ?
(noreal = 0, getreal(tt)) :
ztrdup(tt);
for (p = lsuf; *p && *p != String && *p != Tick; p++);
rsuf = *p ? (noreal = 0, getreal(lsuf)) : ztrdup(lsuf);
/* Check if word is a pattern. */
for (s1 = NULL, sf1 = 0, p = rpre + (rpl = strlen(rpre)) - 1;
p >= rpre && (ispattern != 3 || !sf1);
p--)
if (itok(*p) && (p > rpre || (*p != Equals && *p != Tilde)))
ispattern |= sf1 ? 1 : 2;
else if (*p == '/') {
sf1++;
if (!s1)
s1 = p;
}
for (s2 = NULL, sf2 = t = 0, p = rsuf; *p && (!t || !sf2); p++)
if (itok(*p))
t |= sf2 ? 4 : 2;
else if (*p == '/') {
sf2++;
if (!s2)
s2 = p;
}
ispattern = ispattern | t;
/* But if we were asked not to do glob completion, we never treat the *
* thing as a pattern. */
if (!useglob)
ispattern = 0;
if (ispattern) {
/* The word should be treated as a pattern, so compute the matcher. */
p = (char *)ncalloc(rpl + rsl + 2);
strcpy(p, rpre);
if (rpl && p[rpl - 1] != Star) {
p[rpl] = Star;
strcpy(p + rpl + 1, rsuf);
} else
strcpy(p + rpl, rsuf);
patcomp = parsereg(p);
}
if (!patcomp) {
untokenize(rpre);
untokenize(rsuf);
rpl = strlen(rpre);
rsl = strlen(rsuf);
}
untokenize(lpre);
untokenize(lsuf);
/* Handle completion of files specially (of course). */
if ((cc->mask & (CC_FILES | CC_COMMPATH)) || cc->glob) {
/* s1 and s2 point to the last/first slash in the prefix/suffix. */
if (!s1)
s1 = rpre;
if (!s2)
s2 = rsuf + rsl;
/* Compute the path prefix/suffix. */
if (*s1 != '/')
ppre = ztrdup("");
else {
if ((sav = *s1 ? s1[1] : '\0'))
s1[1] = '\0';
ppre = ztrdup(rpre);
if (sav)
s1[1] = sav;
}
psuf = ztrdup(s2);
/* And get the file prefix. */
fpre = ztrdup(((s1 == s || s1 == rpre || ic) &&
(*s != '/' || cs == wb)) ? s1 : s1 + 1);
/* And the suffix. */
sav = *s2;
*s2 = '\0';
fsuf = ztrdup(rsuf);
*s2 = sav;
if (useglob && (ispattern & 2)) {
int t2;
/* We have to use globbing, so compute the pattern from *
* the file prefix and suffix with a `*' between them. */
p = (char *)ncalloc((t2 = strlen(fpre)) + strlen(fsuf) + 2);
strcpy(p, fpre);
if ((!t2 || p[t2 - 1] != Star) && *fsuf != Star)
p[t2++] = Star;
strcpy(p + t2, fsuf);
filecomp = parsereg(p);
}
if (!filecomp) {
untokenize(fpre);
untokenize(fsuf);
fpl = strlen(fpre);
fsl = strlen(fsuf);
}
addwhat = -1;
/* Completion after `~', maketildelist adds the usernames *
* and named directories. */
if (ic == Tilde)
maketildelist();
else if (ic == Equals) {
/* Completion after `=', get the command names from *
* the cmdnamtab and aliases from aliastab. */
if (isset(HASHLISTALL))
cmdnamtab->filltable(cmdnamtab);
dumphashtable(cmdnamtab, -7);
dumphashtable(aliastab, -2);
} else {
/* Normal file completion... */
if (ispattern & 1) {
/* But with pattern matching. */
LinkList l = newlinklist();
LinkNode n;
int ng = opts[NULLGLOB];
opts[NULLGLOB] = 1;
addwhat = 0;
p = (char *)ncalloc(lpl + lsl + 3);
strcpy(p, lpre);
if (*lsuf != '*' && *lpre && lpre[lpl - 1] != '*')
strcat(p, "*");
strcat(p, lsuf);
if (*lsuf && lsuf[lsl - 1] != '*' && lsuf[lsl - 1] != ')')
strcat(p, "*");
/* Do the globbing. */
tokenize(p);
remnulargs(p);
addlinknode(l, p);
globlist(l);
if (nonempty(l)) {
/* And add the resulting words. */
haswhat |= HAS_PATHPAT;
for (n = firstnode(l); n; incnode(n))
addmatch(getdata(n), NULL);
}
opts[NULLGLOB] = ng;
} else {
/* No pattern matching. */
addwhat = CC_FILES;
prpre = ztrdup(ppre);
if (sf2)
/* We are in the path, so add only directories. */
gen_matches_files(1, 0, 0);
else {
if (cc->mask & CC_FILES)
/* Add all files. */
gen_matches_files(0, 0, 1);
else if (cc->mask & CC_COMMPATH) {
/* Completion of command paths. */
if (sf1)
/* There is a path prefix, so add *
* directories and executables. */
gen_matches_files(1, 1, 0);
else {
/* No path prefix, so add the things *
* reachable via the PATH variable. */
char **pc = path, *pp = prpre;
for (; *pc; pc++)
if (pc[0][0] == '.' && !pc[0][1])
break;
if (*pc) {
prpre = "./";
gen_matches_files(1, 1, 0);
prpre = pp;
}
}
}
/* The compctl has a glob pattern (compctl -g). */
if (cc->glob) {
int ns, pl = strlen(prpre), o;
char *g = dupstring(cc->glob), pa[PATH_MAX];
char *p2, *p3;
int ne = noerrs, md = opts[MARKDIRS];
/* These are used in the globbing code to make *
* things a bit faster. */
glob_pre = fpre;
glob_suf = fsuf;
noerrs = 1;
addwhat = -6;
strcpy(pa, prpre);
o = strlen(pa);
opts[MARKDIRS] = 0;
/* The compctl -g string may contain more than *
* one pattern, so we need a loop. */
while (*g) {
LinkList l = newlinklist();
int ng;
/* Find the blank terminating the pattern. */
while (*g && inblank(*g))
g++;
/* Oops, we already reached the end of the
string. */
if (!*g)
break;
for (p = g + 1; *p && !inblank(*p); p++)
if (*p == '\\' && p[1])
p++;
sav = *p;
*p = '\0';
/* Get the pattern string. */
tokenize(g = dupstring(g));
if (*g == '=')
*g = Equals;
if (*g == '~')
*g = Tilde;
remnulargs(g);
if (*g == Equals || *g == Tilde) {
/* The pattern has a `~' or `=' at the *
* beginning, so we expand this and use *
* the result. */
filesub(&g, 0);
addlinknode(l, dupstring(g));
} else if (*g == '/')
/* The pattern is a full path (starting *
* with '/'), so add it unchanged. */
addlinknode(l, dupstring(g));
else {
/* It's a simple pattern, so append it to *
* the path we have on the command line. */
strcpy(pa + o, g);
addlinknode(l, dupstring(pa));
}
/* Do the globbing. */
ng = opts[NULLGLOB];
opts[NULLGLOB] = 1;
globlist(l);
opts[NULLGLOB] = ng;
/* Get the results. */
if (nonempty(l) && peekfirst(l)) {
for (p2 = (char *)peekfirst(l); *p2; p2++)
if (itok(*p2))
break;
if (!*p2) {
if (*g == Equals || *g == Tilde ||
*g == '/') {
/* IF the pattern started with `~', *
* `=', or `/', add the result only, *
* if it realy matches what we have *
* on the line. */
while ((p2 = (char *)ugetnode(l)))
if (strpfx(prpre, p2))
addmatch(p2 + pl, NULL);
} else {
/* Otherwise ignore the path we *
* prepended to the pattern. */
while ((p2 = p3 =
(char *)ugetnode(l))) {
for (ns = sf1; *p3 && ns; p3++)
if (*p3 == '/')
ns--;
addmatch(p3, NULL);
}
}
}
}
pa[o] = '\0';
*p = sav;
g = p;
}
glob_pre = glob_suf = NULL;
noerrs = ne;
opts[MARKDIRS] = md;
}
}
}
}
}
/* Use tricat() instead of dyncat() to get zalloc()'d memory. */
if (ic) {
/* Now change the `~' and `=' tokens to the real characters so *
* that things starting with these characters will be added. */
char *orpre = rpre;
rpre = tricat("", (ic == Tilde) ? "~" : "=", rpre);
rpl++;
zsfree(orpre);
}
if (!ic && (cc->mask & CC_COMMPATH) && !*ppre && !*psuf) {
/* If we have to complete commands, add alias names, *
* shell functions and builtins too. */
dumphashtable(aliastab, -3);
dumphashtable(reswdtab, -3);
dumphashtable(shfunctab, -3);
dumphashtable(builtintab, -3);
if (isset(HASHLISTALL))
cmdnamtab->filltable(cmdnamtab);
dumphashtable(cmdnamtab, -3);
/* And parameter names if autocd and cdablevars are set. */
if (isset(AUTOCD) && isset(CDABLEVARS))
dumphashtable(paramtab, -4);
}
addwhat = (cc->mask & CC_QUOTEFLAG) ? -2 : CC_QUOTEFLAG;
if (cc->mask & CC_NAMED)
/* Add named directories. */
scanhashtable(nameddirtab, 0, 0, 0, addhnmatch, 0);
if (cc->mask & CC_OPTIONS) {
/* Add option names. */
struct option *o;
for (o = optns + OPT_SIZE; (--o)->name; )
addmatch(dupstring(o->name), NULL);
}
if (cc->mask & CC_VARS)
/* And parameter names. */
dumphashtable(paramtab, -2);
if (cc->mask & CC_BINDINGS) {
/* And zle function names... */
int t0;
for (t0 = 0; t0 != ZLECMDCOUNT; t0++)
if (*zlecmds[t0].name)
addmatch(dupstring(zlecmds[t0].name), NULL);
}
if (cc->keyvar) {
/* This adds things given to the compctl -k flag *
* (from a parameter or a list of words). */
char **usr = get_user_var(cc->keyvar);
if (usr)
while (*usr)
addmatch(*usr++, NULL);
}
if (cc->mask & CC_USERS)
/* Add user names. */
maketildelist();
if (cc->func) {
/* This handles the compctl -K flag. */
List list;
char **r;
int lv = lastval;
/* Get the function. */
if ((list = getshfunc(cc->func))) {
/* We have it, so build a argument list. */
LinkList args = newlinklist();
addlinknode(args, cc->func);
if (*delit) {
sav = os[ooffs];
os[ooffs] = '\0';
p = dupstring(os);
untokenize(p);
addlinknode(args, p);
os[ooffs] = sav;
p = dupstring(os + ooffs);
untokenize(p);
addlinknode(args, p);
} else {
addlinknode(args, lpre);
addlinknode(args, lsuf);
}
/* This flag allows us to use read -l and -c. */
inzlefunc = 1;
/* Call the function. */
doshfunc(list, args, 0, 1);
inzlefunc = 0;
/* And get the result from the reply parameter. */
if ((r = get_user_var("reply")))
while (*r)
addmatch(*r++, NULL);
}
lastval = lv;
}
if (cc->mask & (CC_JOBS | CC_RUNNING | CC_STOPPED)) {
/* Get job names. */
int i;
char *j, *jj;
for (i = 0; i < MAXJOB; i++)
if (jobtab[i].stat & STAT_INUSE) {
int stopped = jobtab[i].stat & STAT_STOPPED;
j = jj = dupstring(jobtab[i].procs->text);
/* Find the first word. */
for (; *jj; jj++)
if (*jj == ' ') {
*jj = '\0';
break;
}
if ((cc->mask & CC_JOBS) ||
(stopped && (cc->mask & CC_STOPPED)) ||
(!stopped && (cc->mask & CC_RUNNING)))
addmatch(j, NULL);
}
}
if (cc->str) {
/* Get the stuff from a compctl -s. */
LinkList foo = newlinklist();
LinkNode n;
int first = 1, ng = opts[NULLGLOB], oowe = we, oowb = wb;
char *tmpbuf;
opts[NULLGLOB] = 1;
/* Put the strin in the lexer buffer and call the lexer to *
* get the words we have to expand. */
zleparse = 1;
lexsave();
tmpbuf = (char *)halloc(strlen(cc->str) + 5);
sprintf(tmpbuf, "foo %s", cc->str); /* KLUDGE! */
inpush(tmpbuf, 0);
strinbeg();
noaliases = 1;
do {
ctxtlex();
if (tok == ENDINPUT || tok == LEXERR)
break;
if (!first && tokstr && *tokstr)
addlinknode(foo, ztrdup(tokstr));
first = 0;
} while (tok != ENDINPUT && tok != LEXERR);
noaliases = 0;
strinend();
inpop();
errflag = zleparse = 0;
lexrestore();
/* Fine, now do full expansion. */
prefork(foo, 0);
if (!errflag) {
globlist(foo);
if (!errflag)
/* And add the resulting words as matches. */
for (n = firstnode(foo); n; incnode(n))
addmatch((char *)n->dat, NULL);
}
opts[NULLGLOB] = ng;
we = oowe;
wb = oowb;
}
if (cc->hpat) {
/* We have a pattern to take things from the history. */
Comp compc = NULL;
char *e, *h, hpatsav;
Histent he;
int i = curhist - 1, n = cc->hnum;
/* Parse the pattern, if it isn't the null string. */
if (*(cc->hpat)) {
char *thpat = dupstring(cc->hpat);
tokenize(thpat);
compc = parsereg(thpat);
}
/* n holds the number of history line we have to search. */
if (!n)
n = -1;
/* Now search the history. */
while (n-- && (he = quietgethist(i--))) {
int iwords;
for (iwords = 0; iwords < he->nwords; iwords++) {
h = he->text + he->words[iwords*2];
e = he->text + he->words[iwords*2+1];
hpatsav = *e;
*e = '\0';
/* We now have a word from the history, ignore it *
* if it begins with a quote or `$'. */
if (*h != '\'' && *h != '"' && *h != '`' && *h != '$' &&
(!compc || domatch(h, compc, 0)))
/* Otherwise add it if it was matched. */
addmatch(dupstring(h), NULL);
if (hpatsav)
*e = hpatsav;
}
}
}
if ((t = cc->mask & (CC_ARRAYS | CC_INTVARS | CC_ENVVARS | CC_SCALARS |
CC_READONLYS | CC_SPECIALS | CC_PARAMS)))
/* Add various flavours of parameters. */
dumphashtable(paramtab, t);
if ((t = cc->mask & CC_SHFUNCS))
/* Add shell functions. */
dumphashtable(shfunctab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
if ((t = cc->mask & CC_BUILTINS))
/* Add builtins. */
dumphashtable(builtintab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
if ((t = cc->mask & CC_EXTCMDS))
/* Add external commands */
dumphashtable(cmdnamtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
if ((t = cc->mask & CC_RESWDS))
/* Add reserved words */
dumphashtable(reswdtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
if ((t = cc->mask & (CC_ALREG | CC_ALGLOB)))
/* Add the two types of aliases. */
dumphashtable(aliastab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS)));
/* If we have no matches, ignore fignore. */
if (empty(matches)) {
matches = fmatches;
firstm = ffirstm;
shortest = fshortest;
ab = fab;
ae = fae;
shortl = fshortl;
}
/* Make an array from the list of matches. */
makearray(matches);
PERMALLOC {
amatches = arrdup(amatches);
/* And quote the prefixes/suffixes. */
if (hasspecial(s)) {
zfree(lpre, lpl);
zfree(lsuf, lsl);
lpre = zalloc(lpl + 1);
memcpy(lpre, s, lpl);
lpre[lpl] = '\0';
lsuf = ztrdup(s + offs);
quotepresuf(&lpre);
quotepresuf(&lsuf);
untokenize(lpre);
untokenize(lsuf);
}
quotepresuf(&fpre);
quotepresuf(&fsuf);
quotepresuf(&ppre);
quotepresuf(&psuf);
} LASTALLOC;
/* Get the explanation string we will have to print. */
expl = cc->explain;
remsuffix = (cc->mask & CC_REMOVE);
ccsuffix = cc->suffix;
validlist = 1;
if(nmatches && !errflag)
return 0;
if ((isf || cc->xor) && !parampre) {
/* We found no matches, but there is a xor'ed completion: *
* fine, so go back and continue with that compctl. */
errflag = 0;
cc = cc->xor;
isf = 0;
wb = owb;
we = owe;
cs = ocs;
strcpy((char *)line, (char *)ol);
offs = oloffs;
s = dupstring(os);
free(amatches);
zsfree(rpre);
zsfree(rsuf);
zsfree(lpre);
zsfree(lsuf);
zsfree(ppre);
zsfree(psuf);
zsfree(fpre);
zsfree(fsuf);
zsfree(prpre);
zsfree(parampre);
zsfree(qparampre);
goto xorrec;
}
return 1;
}
/* Invalidate the completion list. */
/**/
void
invalidatelist(void)
{
if(showinglist == -2)
listmatches();
if(validlist) {
freearray(amatches);
zsfree(rpre);
zsfree(rsuf);
zsfree(lpre);
zsfree(lsuf);
zsfree(ppre);
zsfree(psuf);
zsfree(fpre);
zsfree(fsuf);
zsfree(prpre);
zsfree(parampre);
zsfree(qparampre);
if (ccmain != &cc_dummy)
freecompctl(ccmain);
}
menucmp = showinglist = validlist = 0;
}
/* Get the words from a variable or a compctl -k list. */
/**/
char **
get_user_var(char *nam)
{
if (!nam)
return NULL;
else if (*nam == '(') {
/* It's a (...) list, not a parameter name. */
char *ptr, *s, **uarr, **aptr;
int count = 0, notempty = 0, brk = 0;
LinkList arrlist = newlinklist();
ptr = dupstring(nam);
s = ptr + 1;
while (*++ptr) {
if (*ptr == '\\' && ptr[1])
chuck(ptr), notempty = 1;
else if (*ptr == ',' || inblank(*ptr) || *ptr == ')') {
if (*ptr == ')')
brk++;
if (notempty) {
*ptr = '\0';
count++;
if (*s == '\n')
s++;
addlinknode(arrlist, s);
}
s = ptr + 1;
notempty = 0;
} else {
notempty = 1;
if(*ptr == Meta)
ptr++;
}
if (brk)
break;
}
if (!brk || !count)
return NULL;
*ptr = '\0';
aptr = uarr = (char **)ncalloc(sizeof(char *) * (count + 1));
while ((*aptr++ = (char *)ugetnode(arrlist)));
uarr[count] = NULL;
return uarr;
} else
/* Otherwise it should be a parameter name. */
return getaparam(nam);
}
/* This is strcmp with ignoring backslashes. */
/**/
int
strbpcmp(const void *a, const void *b)
{
char *aa = *((char **)a), *bb = *((char **)b);
while (*aa && *bb) {
if (*aa == '\\')
aa++;
if (*bb == '\\')
bb++;
if (*aa != *bb)
return (int)(*aa - *bb);
if (*aa)
aa++;
if (*bb)
bb++;
}
return (int)(*aa - *bb);
}
/* Make an array from a linked list */
/**/
void
makearray(LinkList l)
{
char **ap, **bp, **cp;
LinkNode nod;
/* Build an array for the matches. */
ap = amatches = (char **)ncalloc(((nmatches = countlinknodes(l)) + 1) *
sizeof(char *));
/* And copy them into it. */
for (nod = firstnode(l); nod; incnode(nod))
*ap++ = (char *)getdata(nod);
*ap = NULL;
/* Now sort the array. */
qsort((void *) amatches, nmatches, sizeof(char *),
(int (*) _((const void *, const void *)))strbpcmp);
/* And delete the ones that occur more than once. */
for (ap = cp = amatches; *ap; ap++) {
*cp++ = *ap;
for (bp = ap; bp[1] && !strcmp(*ap, bp[1]); bp++);
ap = bp;
}
*cp = NULL;
nmatches = arrlen(amatches);
}
/* Handle the case were we found more than one match. */
/**/
void
do_ambiguous(void)
{
int p = (usemenu || ispattern), atend = (cs == we);
int inv = 0;
menucmp = 0;
/* If we have to insert the first match, call do_single(). This is *
* how REC_EXACT takes effect. We effectively turn the ambiguous *
* completion into an unambiguous one. */
if (shortest && shortl == 0 && isset(RECEXACT) &&
(usemenu == 0 || unset(AUTOMENU))) {
do_single(shortest);
invalidatelist();
return;
}
/* Setting lastambig here means that the completion is ambiguous and *
* AUTO_MENU might want to start a menu completion next time round, *
* but this might be overridden below if we can complete an *
* unambiguous prefix. */
lastambig = 1;
if(p) {
/* p is set if we are in a position to start using menu completion *
* due to one of the menu completion options, or due to the *
* menu-complete-word command, or due to using GLOB_COMPLETE which *
* does menu-style completion regardless of the setting of the *
* normal menu completion options. */
do_ambig_menu();
} else {
/* Sort-of general case: we have an ambiguous completion, and aren't *
* starting menu completion or doing anything really weird. We need *
* to insert any unambiguous prefix and suffix, if possible. */
complexpect = 0;
if(ab)
inststrlen(firstm, 1, ab);
if(ae && !atend)
inststrlen(firstm + strlen(firstm) - ae, 0, ae);
if(ab || (ae && !atend))
inv = 1;
/* If the LIST_AMBIGUOUS option (meaning roughly `show a list only *
* if the completion is completely ambiguous') is set, and some *
* prefix was inserted, return now, bypassing the list-displaying *
* code. On the way, invalidate the list and note that we don't *
* want to enter an AUTO_MENU imediately. */
if(isset(LISTAMBIGUOUS) && inv) {
invalidatelist();
lastambig = 0;
return;
}
}
/* At this point, we might want a completion listing. Show the listing *
* if it is needed. */
if (unset(NOLISTBEEP))
feep();
if (isset(AUTOLIST) && !amenu && !showinglist)
showinglist = -2;
if(inv)
invalidatelist();
}
/* This is a stat that ignores backslashes in the filename. The `ls' *
* parameter says if we have to do lstat() or stat(). I think this *
* should instead be done by use of a general function to expand a *
* filename (stripping backslashes), combined with the actual *
* (l)stat(). */
/**/
int
ztat(char *nam, struct stat *buf, int ls)
{
char b[PATH_MAX], *p;
for (p = b; p < b + sizeof(b) - 1 && *nam; nam++)
if (*nam == '\\' && nam[1])
*p++ = *++nam;
else
*p++ = *nam;
*p = '\0';
return ls ? lstat(b, buf) : stat(b, buf);
}
/* Insert a single match in the command line. */
/**/
void
do_single(char *str)
{
int ccs, l, insc = 0, inscs = 0;
char singlec = ' ';
if (!ccsuffix || !(haswhat & HAS_SUFFIX) || !remsuffix)
addedsuffix = 0;
if (!menucur) {
/* We are currently not in a menu-completion, *
* so set the position variables. */
menuinsc = 0;
if (ispattern) {
cs = we;
menupos = wb;
} else
menupos = cs;
menuwe = (cs == we);
if (ccsuffix && !(haswhat & HAS_SUFFIX)) {
/* Add a compctl -S suffix if we have one. */
if (*ccsuffix) {
ccs = cs;
cs = we;
inststrlen(ccsuffix, menuwe, -1);
menuend = cs;
cs = ccs;
if (remsuffix)
/* addedsuffix is used by the key input handling *
* code to find out if it has to delete a suffix. */
addedsuffix = strlen(ccsuffix);
} else
menuend = we;
haswhat |= HAS_SUFFIX;
} else
menuend = we;
}
ccs = cs;
/* If we are already in a menu-completion or if we have done a *
* glob completion, we have to delete some of the stuff on the *
* command line. */
if (menucur) {
if (menuinsc) {
cs = menuend;
foredel(1);
}
l = menulen;
} else if (ispattern)
l = we - wb;
else
l = 0;
cs = menupos;
menuinsc = 0;
if (l) {
foredel(l);
if (menuwe)
ccs -= l;
menuend -= l;
}
/* And than we insert the new string. */
inststrlen(str, 1, menulen = strlen(str));
/* And move the cursor and adjust the menuend variable. */
if (menuwe)
cs = ccs + menulen;
menuend += menulen;
if (!(haswhat & HAS_SUFFIX)) {
/* There is no suffix, so we may add one. */
if (!(haswhat & HAS_MISC) || (parampre && isset(AUTOPARAMSLASH))) {
/* If we have only filenames or we completed a parameter name *
* and auto_param_slash is set, lets see if it is a directory. */
char *p;
struct stat buf;
/* Build the path name. */
if (ispattern || ic || parampre) {
int ne = noerrs;
noerrs = 1;
if (parampre) {
int pl = strlen(parampre);
p = (char *) ncalloc(pl + strlen(lpre) + strlen(str) +
strlen(lsuf) + 1);
sprintf(p, "%s%s%s%s", parampre, lpre, str, lsuf);
if (pl && p[pl-1] == Inbrace)
strcpy(p+pl-1, p+pl);
}
else if (ic) {
p = (char *) ncalloc(strlen(ppre) + strlen(fpre) + strlen(str) +
strlen(fsuf) + strlen(psuf) + 2);
sprintf(p, "%c%s%s%s%s%s", ic,
ppre, fpre, str, fsuf, psuf);
}
else
p = dupstring(str);
parsestr(p);
if (ic)
*p = ic;
singsub(&p);
noerrs = ne;
} else {
p = (char *) ncalloc((prpre ? strlen(prpre) : 0) + strlen(fpre) +
strlen(str) + strlen(fsuf) + strlen(psuf) + 3);
sprintf(p, "%s%s%s%s%s",
(prpre && *prpre) ? prpre : "./", fpre, str,
fsuf, psuf);
}
/* And do the stat. */
if (!ztat(p, &buf, 0) && (buf.st_mode & S_IFMT) == S_IFDIR) {
/* It is a directory, so prepare to add *
* the slash and set addedsuffix. */
singlec = '/';
if (menuwe || isset(ALWAYSTOEND))
addedsuffix = isset(AUTOREMOVESLASH) ? 1 : 0;
}
}
if (menuend > ll)
menuend = ll;
if (menuend && ((((char)line[menuend - 1]) != singlec) ||
(menuend > 1 && singlec == ' ' &&
(line[menuend - 2] == '\\' || line[menuend - 2] == STOUC(Meta)))))
if (parampre && singlec == '/' && ((char)line[menuend]) == '/')
addedsuffix = 0;
/* Now insert the slash or space if there is none already. */
else {
ccs = cs;
cs = menuend;
inststrlen((char *)&singlec, 1, 1);
insc = 1;
if (singlec != ' ')
menuinsc = 1;
inscs = cs;
if (!menuwe)
cs = ccs;
}
}
/* Move to the end of the word if requested. */
if (isset(ALWAYSTOEND) || menuwe)
cs = menuend + (!(haswhat & HAS_SUFFIX) && insc);
if (menucmp && singlec == ' ' && !(haswhat & HAS_SUFFIX)) {
/* Get rid of the added space if we are doing menucompletion. */
if (insc) {
ccs = cs;
cs = inscs;
backdel(1);
if (ccs != inscs)
cs = ccs;
} else
cs--;
}
}
/* This handles the beginning of menu-completion. */
/**/
void
do_ambig_menu(void)
{
menucmp = 1;
menucur = NULL;
do_single(amatches[0]);
menucur = amatches;
}
/* Return non-zero if s is a prefix of t. */
/**/
int
strpfx(char *s, char *t)
{
while (*s && *s == *t)
s++, t++;
return !*s;
}
/* Return non-zero if s is a suffix of t. */
/**/
int
strsfx(char *s, char *t)
{
int ls = strlen(s), lt = strlen(t);
if (ls <= lt)
return !strcmp(t + lt - ls, s);
return 0;
}
/* Return the length of the common prefix of s and t. */
/**/
int
pfxlen(char *s, char *t)
{
int i = 0;
while (*s && *s == *t)
s++, t++, i++;
return i;
}
/* Return the length of the common suffix of s and t. */
/**/
int
sfxlen(char *s, char *t)
{
if (*s && *t) {
int i = 0;
char *s2 = s + strlen(s) - 1, *t2 = t + strlen(t) - 1;
while (s2 >= s && t2 >= t && *s2 == *t2)
s2--, t2--, i++;
return i;
} else
return 0;
}
/* This is used to print the explanation string. *
* It returns the number of lines printed. */
/**/
int
printfmt(char *fmt, int n, int dopr)
{
char *p = fmt, nc[DIGBUFSIZE];
int l = 0, cc = 0;
for (; *p; p++) {
/* Handle the `%' stuff (%% == %, %n == <number of matches>). */
if (*p == '%') {
if (*++p) {
switch (*p) {
case '%':
if (dopr)
putc('%', shout);
cc++;
break;
case 'n':
sprintf(nc, "%d", n);
if (dopr)
fprintf(shout, nc);
cc += strlen(nc);
break;
}
} else
break;
} else {
cc++;
if (*p == '\n') {
l += 1 + (cc / columns);
cc = 0;
}
if (dopr)
putc(*p, shout);
}
}
return l + (cc / columns);
}
/* List the matches. Note that the list entries are metafied. */
/**/
void
listmatches(void)
{
int longest = 1, fct, fw, colsz, t0, t1, ct, up, cl, xup = 0;
int off, boff, nboff;
int of = (isset(LISTTYPES) && !(haswhat & HAS_MISC));
char **arr, **ap, sav;
int nfpl, nfsl, nlpl, nlsl;
int listmax = getiparam("LISTMAX");
#ifdef DEBUG
/* Sanity check */
if(!validlist) {
trashzle();
fputs("BUG: listmatches called with bogus list\n", shout);
showinglist = 0;
return;
}
#endif
/* Calculate lengths of prefixes/suffixes to be added */
nfpl = fpre ? niceztrlen(fpre) : 0;
nfsl = fsuf ? niceztrlen(fsuf) : 0;
nlpl = lpre ? niceztrlen(lpre) : 0;
nlsl = lsuf ? niceztrlen(lsuf) : 0;
/* Calculate the lengths of the prefixes/suffixes we have to ignore
during printing. */
off = ispattern && ppre && *ppre &&
!(haswhat & (HAS_MISC | HAS_PATHPAT)) ? strlen(ppre) : 0;
boff = ispattern && psuf && *psuf &&
!(haswhat & (HAS_MISC | HAS_PATHPAT)) ? strlen(psuf) : 0;
nboff = ispattern && psuf && *psuf &&
!(haswhat & (HAS_MISC | HAS_PATHPAT)) ? niceztrlen(psuf) : 0;
/* When called from expandorcompleteprefix, we probably have to
remove a space now. */
if (remove_at >= 0) {
int ocs = cs;
cs = remove_at;
deletechar();
remove_at = -1;
cs = ocs;
}
/* Set the cursor below the prompt. */
trashzle();
ct = nmatches;
showinglist = 0;
clearflag = (isset(USEZLE) && termok &&
(isset(ALWAYSLASTPROMPT) && zmult == 1)) ||
(unset(ALWAYSLASTPROMPT) && zmult != 1);
arr = amatches;
/* Calculate the column width, the number of columns and the number
of lines. */
for (ap = arr; *ap; ap++)
if ((cl = niceztrlen(*ap + off) - nboff +
(ispattern ? 0 :
(!(haswhat & HAS_MISC) ? nfpl + nfsl : nlpl + nlsl))) > longest)
longest = cl;
if (of)
longest++;
fw = longest + 2;
fct = (columns + 1) / fw;
if (fct == 0) {
fct = 1;
colsz = ct;
up = colsz + nlnct - clearflag;
for (ap = arr; *ap; ap++)
up += (niceztrlen(*ap + off) - nboff + of +
(ispattern ? 0 :
(!(haswhat & HAS_MISC) ? nfpl + nfsl : nlpl + nlsl))) / columns;
} else {
colsz = (ct + fct - 1) / fct;
up = colsz + nlnct - clearflag;
}
/* Print the explanation string, if any. */
if (expl) {
xup = printfmt(expl, ct, 1) + 1;
putc('\n', shout);
up += xup;
}
/* Maybe we have to ask if the user wants to see the list. */
if ((listmax && ct > listmax) || (!listmax && up >= lines)) {
int qup;
setterm();
qup = printfmt("zsh: do you wish to see all %n possibilities? ", ct, 1);
fflush(shout);
if (getzlequery() != 'y') {
if (clearflag) {
putc('\r', shout);
tcmultout(TCUP, TCMULTUP, qup);
if (tccan(TCCLEAREOD))
tcout(TCCLEAREOD);
tcmultout(TCUP, TCMULTUP, nlnct + xup);
} else
putc('\n', shout);
return;
}
if (clearflag) {
putc('\r', shout);
tcmultout(TCUP, TCMULTUP, qup);
if (tccan(TCCLEAREOD))
tcout(TCCLEAREOD);
} else
putc('\n', shout);
settyinfo(&shttyinfo);
}
/* Now print the matches. */
for (t1 = 0; t1 != colsz; t1++) {
ap = arr + t1;
if (of) {
/* We have to print the file types. */
while (*ap) {
int t2;
char *pb;
struct stat buf;
/* Build the path name for the stat. */
if (ispattern) {
int cut = strlen(*ap) - boff;
sav = ap[0][cut];
ap[0][cut] = '\0';
nicezputs(*ap + off, shout);
t2 = niceztrlen(*ap + off);
ap[0][cut] = sav;
pb = *ap;
} else {
nicezputs(fpre, shout);
nicezputs(*ap, shout);
nicezputs(fsuf, shout);
t2 = nfpl + niceztrlen(*ap) + nfsl;
pb = (char *) ncalloc((prpre ? strlen(prpre) : 0) + 3 +
strlen(fpre) + strlen(*ap) + strlen(fsuf));
sprintf(pb, "%s%s%s%s",
(prpre && *prpre) ? prpre : "./", fpre, *ap, fsuf);
}
if (ztat(pb, &buf, 1))
putc(' ', shout);
else
/* Print the file type character. */
putc(file_type(buf.st_mode), shout);
for (t0 = colsz; t0 && *ap; t0--, ap++);
if (*ap)
/* And add spaces to make the columns aligned. */
for (++t2; t2 < fw; t2++)
putc(' ', shout);
}
} else
while (*ap) {
int t2;
if (ispattern) {
int cut = strlen(*ap) - boff;
sav = ap[0][cut];
ap[0][cut] = '\0';
nicezputs(*ap + off, shout);
t2 = niceztrlen(*ap + off);
ap[0][cut] = sav;
} else if (!(haswhat & HAS_MISC)) {
nicezputs(fpre, shout);
nicezputs(*ap, shout);
nicezputs(fsuf, shout);
t2 = nfpl + niceztrlen(*ap) + nfsl;
} else {
nicezputs(lpre, shout);
nicezputs(*ap, shout);
nicezputs(lsuf, shout);
t2 = nlpl + niceztrlen(*ap) + nlsl;
}
for (t0 = colsz; t0 && *ap; t0--, ap++);
if (*ap)
for (; t2 < fw; t2++)
putc(' ', shout);
}
if (t1 != colsz - 1 || !clearflag)
putc('\n', shout);
}
if (clearflag)
/* Move the cursor up to the prompt, if always_last_prompt *
* is set and all that... */
if (up < lines) {
tcmultout(TCUP, TCMULTUP, up);
showinglist = -1;
} else
clearflag = 0, putc('\n', shout);
}
/* This is used to print expansions. */
/**/
void
listlist(LinkList l)
{
int hw = haswhat, ip = ispattern;
char *lp = lpre, *ls = lsuf;
int nm = nmatches, vl = validlist;
char **am = amatches;
char *ex = expl;
haswhat = HAS_MISC;
ispattern = 0;
validlist = 1;
lpre = lsuf = "";
expl = NULL;
makearray(l);
listmatches();
showinglist = 0;
expl = ex;
amatches = am;
nmatches = nm;
validlist = vl;
lpre = lp;
lsuf = ls;
ispattern = ip;
haswhat = hw;
}
/* And this is used to print select lists. Hm, this function should *
* probably be move to loop.c. */
/**/
void
selectlist(LinkList l)
{
int longest = 1, fct, fw = 0, colsz, t0, t1, ct;
LinkNode n;
char **arr, **ap;
trashzle();
ct = countlinknodes(l);
ap = arr = (char **)alloc((countlinknodes(l) + 1) * sizeof(char **));
for (n = (LinkNode) firstnode(l); n; incnode(n))
*ap++ = (char *)getdata(n);
*ap = NULL;
for (ap = arr; *ap; ap++)
if (strlen(*ap) > longest)
longest = strlen(*ap);
t0 = ct;
longest++;
while (t0)
t0 /= 10, longest++;
/* to compensate for added ')' */
fct = (columns - 1) / (longest + 3);
if (fct == 0)
fct = 1;
else
fw = (columns - 1) / fct;
colsz = (ct + fct - 1) / fct;
for (t1 = 0; t1 != colsz; t1++) {
ap = arr + t1;
do {
int t2 = strlen(*ap) + 2, t3;
fprintf(stderr, "%d) %s", t3 = ap - arr + 1, *ap);
while (t3)
t2++, t3 /= 10;
for (; t2 < fw; t2++)
fputc(' ', stderr);
for (t0 = colsz; t0 && *ap; t0--, ap++);
}
while (*ap);
fputc('\n', stderr);
}
/* Below is a simple attempt at doing it the Korn Way..
ap = arr;
t0 = 0;
do {
t0++;
fprintf(stderr,"%d) %s\n",t0,*ap);
ap++;
}
while (*ap);*/
fflush(stderr);
}
/* Expand the history references. */
/**/
int
doexpandhist(void)
{
unsigned char *ol;
int oll, ocs, ne = noerrs, err;
DPUTS(useheap, "BUG: useheap in doexpandhist()");
HEAPALLOC {
pushheap();
metafy_line();
oll = ll;
ocs = cs;
ol = (unsigned char *)dupstring((char *)line);
expanding = 1;
excs = cs;
ll = cs = 0;
lexsave();
inpush((char *) ol, 0); /* We push ol as it will remain unchanged */
strinbeg();
noaliases = 1;
noerrs = 1;
exlast = inbufct;
do {
ctxtlex();
} while (tok != ENDINPUT && tok != LEXERR);
stophist = 2;
while (!lexstop)
hgetc();
/* We have to save errflags because it's reset in lexrestore. Since *
* noerrs was set to 1 errflag is true if there was a habort() which *
* means that the expanded string is unusable. */
err = errflag;
noerrs = ne;
noaliases = 0;
strinend();
inpop();
zleparse = 0;
lexrestore();
expanding = 0;
if (!err) {
cs = excs;
if (strcmp((char *)line, (char *)ol)) {
unmetafy_line();
/* For vi mode -- reset the beginning-of-insertion pointer *
* to the beginning of the line. This seems a little silly, *
* if we are, for example, expanding "exec !!". */
if (viinsbegin > findbol())
viinsbegin = findbol();
popheap();
LASTALLOC_RETURN 1;
}
}
strcpy((char *)line, (char *)ol);
ll = oll;
cs = ocs;
unmetafy_line();
popheap();
} LASTALLOC;
return 0;
}
/**/
void
magicspace(void)
{
c = ' ';
selfinsert();
doexpandhist();
}
/**/
void
expandhistory(void)
{
if (!doexpandhist())
feep();
}
static int cmdwb, cmdwe;
/**/
char *
getcurcmd(void)
{
int curlincmd;
char *s = NULL;
DPUTS(useheap, "BUG: useheap in getcurcmd()");
HEAPALLOC {
zleparse = 1;
lexsave();
inpush(dupstrspace((char *) line), 0);
strinbeg();
pushheap();
do {
curlincmd = incmdpos;
ctxtlex();
if (tok == ENDINPUT || tok == LEXERR)
break;
if (tok == STRING && curlincmd) {
zsfree(s);
s = ztrdup(tokstr);
cmdwb = ll - wordbeg;
cmdwe = ll + 1 - inbufct;
}
}
while (tok != ENDINPUT && tok != LEXERR && zleparse);
popheap();
strinend();
inpop();
errflag = zleparse = 0;
lexrestore();
} LASTALLOC;
return s;
}
/**/
void
processcmd(void)
{
char *s, *t;
s = getcurcmd();
if (!s) {
feep();
return;
}
t = zlecmds[bindk].name;
zmult = 1;
PERMALLOC {
pushline();
} LASTALLOC;
sizeline(strlen(s) + strlen(t) + 1);
strcpy((char *)line, t);
strcat((char *)line, " ");
cs = ll = strlen((char *)line);
inststr(s);
zsfree(s);
done = 1;
}
/**/
void
expandcmdpath(void)
{
int oldcs = cs, na = noaliases;
char *s, *str;
noaliases = 1;
s = getcurcmd();
noaliases = na;
if (!s || cmdwb < 0 || cmdwe < cmdwb) {
feep();
return;
}
str = findcmd(s);
zsfree(s);
if (!str) {
feep();
return;
}
cs = cmdwb;
foredel(cmdwe - cmdwb);
spaceinline(strlen(str));
strncpy((char *)line + cs, str, strlen(str));
cs = oldcs;
if (cs >= cmdwe - 1)
cs += cmdwe - cmdwb + strlen(str);
if (cs > ll)
cs = ll;
zsfree(str);
}
/* Extra function added by AR Iano-Fletcher. */
/* This is a expand/complete in the vein of wash. */
/**/
void
expandorcompleteprefix(void)
{
/* global c is the current character typed. */
int csafe = c;
/* insert a space and backspace. */
c = ' ';
selfinsert(); /* insert the extra character */
forwardchar(); /* move towards beginning */
remove_at = cs;
/* do the expansion/completion. */
c = csafe;
zmult = 1;
expandorcomplete(); /* complete. */
zmult = -1;
/* remove the inserted space. */
if (remove_at >= 0) {
backwardchar(); /* move towards ends */
deletechar(); /* delete the added space. */
}
remove_at = -1;
}